Skip to content

Commit 862a2d9

Browse files
committed
Merge pull request #13 from WeatherGod/key_change
ENH: Implement key changing
2 parents 68b4995 + 2745016 commit 862a2d9

File tree

2 files changed

+71
-2
lines changed

2 files changed

+71
-2
lines changed

cycler.py

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,14 +117,18 @@ def __init__(self, left, right=None, op=None):
117117
if isinstance(left, Cycler):
118118
self._left = Cycler(left._left, left._right, left._op)
119119
elif left is not None:
120-
self._left = list(left)
120+
# Need to copy the dictionary or else that will be a residual
121+
# mutable that could lead to strange errors
122+
self._left = [copy.copy(v) for v in left]
121123
else:
122124
self._left = None
123125

124126
if isinstance(right, Cycler):
125127
self._right = Cycler(right._left, right._right, right._op)
126128
elif right is not None:
127-
self._right = list(right)
129+
# Need to copy the dictionary or else that will be a residual
130+
# mutable that could lead to strange errors
131+
self._right = [copy.copy(v) for v in right]
128132
else:
129133
self._right = None
130134

@@ -138,6 +142,41 @@ def keys(self):
138142
"""
139143
return set(self._keys)
140144

145+
def change_key(self, old, new):
146+
"""
147+
Change a key in this cycler to a new name.
148+
Modification is performed in-place.
149+
150+
Does nothing if the old key is the same as the new key.
151+
Raises a ValueError if the new key is already a key.
152+
Raises a KeyError if the old key isn't a key.
153+
154+
"""
155+
if old == new:
156+
return
157+
if new in self._keys:
158+
raise ValueError("Can't replace %s with %s, %s is already a key" %
159+
(old, new, new))
160+
if old not in self._keys:
161+
raise KeyError("Can't replace %s with %s, %s is not a key" %
162+
(old, new, old))
163+
164+
self._keys.remove(old)
165+
self._keys.add(new)
166+
167+
if self._right is not None and old in self._right.keys:
168+
self._right.change_key(old, new)
169+
170+
# self._left should always be non-None
171+
# if self._keys is non-empty.
172+
elif isinstance(self._left, Cycler):
173+
self._left.change_key(old, new)
174+
else:
175+
# It should be completely safe at this point to
176+
# assume that the old key can be found in each
177+
# iteration.
178+
self._left = [{new: entry[old]} for entry in self._left]
179+
141180
def _compose(self):
142181
"""
143182
Compose the 'left' and 'right' components of this cycle

test_cycler.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,36 @@ def test_copying():
216216
assert_equal(c3, cycler('foo', [['y', 'g', 'blue'], ['b', 'k']]))
217217

218218

219+
def test_keychange():
220+
c1 = cycler('c', 'rgb')
221+
c2 = cycler('lw', [1, 2, 3])
222+
c3 = cycler('ec', 'yk')
223+
224+
c3.change_key('ec', 'edgecolor')
225+
assert_equal(c3, cycler('edgecolor', c3))
226+
227+
c = c1 + c2
228+
c.change_key('lw', 'linewidth')
229+
# Changing a key in one cycler should have no
230+
# impact in the original cycler.
231+
assert_equal(c2, cycler('lw', [1, 2, 3]))
232+
assert_equal(c, c1 + cycler('linewidth', c2))
233+
234+
c = (c1 + c2) * c3
235+
c.change_key('c', 'color')
236+
assert_equal(c1, cycler('c', 'rgb'))
237+
assert_equal(c, (cycler('color', c1) + c2) * c3)
238+
239+
# Perfectly fine, it is a no-op
240+
c.change_key('color', 'color')
241+
assert_equal(c, (cycler('color', c1) + c2) * c3)
242+
243+
# Can't change a key to one that is already in there
244+
assert_raises(ValueError, Cycler.change_key, c, 'color', 'lw')
245+
# Can't change a key you don't have
246+
assert_raises(KeyError, Cycler.change_key, c, 'c', 'foobar')
247+
248+
219249
def _eq_test_helper(a, b, res):
220250
if res:
221251
assert_equal(a, b)

0 commit comments

Comments
 (0)