-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathfup.py
More file actions
132 lines (104 loc) · 4.77 KB
/
fup.py
File metadata and controls
132 lines (104 loc) · 4.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# -*- coding: utf-8 -*-
"""Functionally update sequences and mappings."""
__all__ = ["fupdate"]
from collections.abc import Iterable, Sequence
from copy import copy
from typing import Any, TypeVar
from .collections import frozendict, ShadowedSequence
T = TypeVar('T')
def fupdate(target: T, indices: "int | slice | Sequence[int | slice] | None" = None, values: Any = None, **bindings: Any) -> T:
"""Return a functionally updated copy of a sequence or a mapping.
The input can be mutable or immutable; it does not matter.
**For mappings**, ``fupdate`` supports any mutable mapping that has an
``.update(**kwargs)`` method (such as ``dict``), and the immutable mapping
``unpythonic.collections.frozendict``.
By design, the behavior of ``fupdate`` differs from ``collections.ChainMap``.
Whereas ``ChainMap`` keeps references to the original mappings, ``fupdate``
makes a shallow copy, to prevent any later mutations of the original from
affecting the functionally updated copy.
**For sequences**, the requirement is that the target's type must provide
a way to construct an instance from an iterable.
We first check whether target's type provides ``._make(iterable)``,
and if so, call that to build the output. Otherwise, we call the
regular constructor, which must then accept a single iterable argument.
In Python's standard library, the ``._make`` mechanism is used by classes
created by ``collections.namedtuple``.
Parameters:
target: sequence or mapping
The target to be functionally updated.
If ``target`` is a sequence:
indices: t or sequence of t, where t: int or slice
The index or indices where ``target`` will be updated.
If a sequence of t, applied left to right.
values: one item or sequence
The corresponding values.
If ``target`` is a mapping:
Use the kwargs syntax to provide any number of ``key=new_value`` pairs.
Returns:
The updated sequence or mapping.
The input is never mutated, and it is **always** shallow-copied, so any
later mutations to the original do not affect the functionally updated
copy.
Also, the invariant ``type(output) is type(input)`` holds.
**Examples**::
lst = [1, 2, 3]
out = fupdate(lst, 1, 42)
assert lst == [1, 2, 3]
assert out == [1, 42, 3]
from itertools import repeat
lst = (1, 2, 3, 4, 5)
out = fupdate(lst, slice(1, 5, 2), tuple(repeat(10, 2)))
assert lst == (1, 2, 3, 4, 5)
assert out == (1, 10, 3, 10, 5)
# a sequence of indices
lst = (1, 2, 3, 4, 5)
out = fupdate(lst, (1, 2, 3), (17, 23, 42))
assert lst == (1, 2, 3, 4, 5)
assert out == (1, 17, 23, 42, 5)
# a sequence of slices
lst = tuple(range(10))
out = fupdate(lst, (slice(0, 10, 2), slice(1, 10, 2)),
(tuple(repeat(2, 5)), tuple(repeat(3, 5))))
assert lst == tuple(range(10))
assert out == (2, 3, 2, 3, 2, 3, 2, 3, 2, 3)
# mix and match
lst = tuple(range(10))
out = fupdate(lst, (slice(0, 10, 2), slice(1, 10, 2), 6),
(tuple(repeat(2, 5)), tuple(repeat(3, 5)), 42))
assert lst == tuple(range(10))
assert out == (2, 3, 2, 3, 2, 3, 42, 3, 2, 3)
from collections import namedtuple
A = namedtuple("A", "p q")
a = A(17, 23)
out = fupdate(a, 0, 42)
assert a == A(17, 23)
assert out == A(42, 23)
d1 = {'foo': 'bar', 'fruit': 'apple'}
d2 = fupdate(d1, foo='tavern')
assert sorted(d1.items()) == [('foo', 'bar'), ('fruit', 'apple')]
assert sorted(d2.items()) == [('foo', 'tavern'), ('fruit', 'apple')]
"""
if indices is not None and bindings:
raise ValueError("Cannot use both indices and bindings.")
if indices is not None:
def make_output(seq: Iterable) -> T:
cls = type(target)
ctor = cls._make if hasattr(cls, "_make") else cls # namedtuple support
gen = (x for x in seq)
return ctor(gen)
if not isinstance(indices, (list, tuple)):
# one index (or slice), value(s) pair only
return make_output(ShadowedSequence(target, indices, values))
seq = target
for index, value in zip(indices, values):
seq = ShadowedSequence(seq, index, value)
return make_output(seq)
if bindings:
if isinstance(target, frozendict):
cls = type(target) # subclassing is possible...
return cls(target, **bindings)
# assume mutable mapping
t = copy(target)
t.update(**bindings)
return t
return copy(target)