Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 36 additions & 14 deletions cycler.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,26 @@ def __init__(self, left, right=None, op=None):
Do not use this directly, use `cycler` function instead.
"""
self._keys = _process_keys(left, right)
self._left = copy.deepcopy(left)
self._right = copy.deepcopy(right)
if isinstance(left, Cycler):
self._left = left._shallow_copy()
else:
self._left = copy.copy(left)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't deep enough to protect against changing the key, but this depends on how #13 ends up being implemented

In [80]: dd = [{a: a} for a in range(3)]

In [81]: import copy

In [82]: dd2 = copy.copy(dd)

In [83]: dd[0][0] = 'foobare'

In [84]: dd2
Out[84]: [{0: 'foobare'}, {1: 1}, {2: 2}]

I think this needs to be

self._left = [dict(v) for v in left]


if isinstance(right, Cycler):
self._right = right._shallow_copy()
else:
self._right = copy.copy(right)

self._op = op

def _shallow_copy(self):
ret = Cycler(None)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can do this block as return Cycler(self._left, self._right, self.op)

ps, sorry for the backwards lisp

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we would need to expand the constructor to allow lists of dictionaries and update _process_keys() to recognize that.

ret._keys = self.keys
ret._left = copy.copy(self._left)
ret._right = copy.copy(self._right)
ret._op = copy.copy(self._op)
return ret

@property
def keys(self):
"""
Expand Down Expand Up @@ -228,11 +244,14 @@ def __iadd__(self, other):
other : Cycler
The second Cycler
"""
old_self = copy.deepcopy(self)
if not isinstance(other, Cycler):
raise TypeError("Cannot += with a non-Cycler object")
# True shallow copy of self is fine since this is in-place
old_self = copy.copy(self)
self._keys = _process_keys(old_self, other)
self._left = old_self
self._op = zip
self._right = copy.deepcopy(other)
self._right = other._shallow_copy()
return self

def __imul__(self, other):
Expand All @@ -244,12 +263,14 @@ def __imul__(self, other):
other : Cycler
The second Cycler
"""

old_self = copy.deepcopy(self)
if not isinstance(other, Cycler):
raise TypeError("Cannot *= with a non-Cycler object")
# True shallow copy of self is fine since this is in-place
old_self = copy.copy(self)
self._keys = _process_keys(old_self, other)
self._left = old_self
self._op = product
self._right = copy.deepcopy(other)
self._right = other._shallow_copy()
return self

def __eq__(self, other):
Expand Down Expand Up @@ -354,7 +375,7 @@ def cycler(*args, **kwargs):
Parameters
----------
arg : Cycler
Copy constructor for Cycler.
Copy constructor for Cycler (does a shallow copy of iterables).

label : name
The property key. In the 2-arg form of the function,
Expand All @@ -363,6 +384,8 @@ def cycler(*args, **kwargs):

itr : iterable
Finite length iterable of the property values.
Can be a single-property `Cycler` that would
be like a key change, but as a shallow copy.

Returns
-------
Expand All @@ -378,7 +401,7 @@ def cycler(*args, **kwargs):
if not isinstance(args[0], Cycler):
raise TypeError("If only one positional argument given, it must "
" be a Cycler instance.")
return copy.deepcopy(args[0])
return Cycler(args[0])
elif len(args) == 2:
return _cycler(*args)
elif len(args) > 2:
Expand Down Expand Up @@ -415,10 +438,9 @@ def _cycler(label, itr):
msg = "Can not create Cycler from a multi-property Cycler"
raise ValueError(msg)

if label in keys:
return copy.deepcopy(itr)
else:
lab = keys.pop()
itr = list(v[lab] for v in itr)
lab = keys.pop()
# Doesn't need to be a new list because
# _from_iter() will be creating that new list anyway.
itr = (v[lab] for v in itr)

return Cycler._from_iter(label, itr)
38 changes: 38 additions & 0 deletions test_cycler.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,44 @@ def test_call():
assert_equal(j, len(c) * 2)


def test_copying():
# Just about everything results in copying the cycler and
# its contents (shallow). This set of tests is intended to make sure
# of that. Our iterables will be mutable for extra fun!
i1 = [1, 2, 3]
i2 = ['r', 'g', 'b']
# For more mutation fun!
i3 = [['y', 'g'], ['b', 'k']]

c1 = cycler('c', i1)
c2 = cycler('lw', i2)
c3 = cycler('foo', i3)

c_before = (c1 + c2) * c3

i1.pop()
i2.append('cyan')
i3[0].append('blue')

c_after = (c1 + c2) * c3

assert_equal(c1, cycler('c', [1, 2, 3]))
assert_equal(c2, cycler('lw', ['r', 'g', 'b']))
assert_equal(c3, cycler('foo', [['y', 'g', 'blue'], ['b', 'k']]))
assert_equal(c_before, (cycler(c=[1, 2, 3], lw=['r', 'g', 'b']) *
cycler('foo', [['y', 'g', 'blue'], ['b', 'k']])))
assert_equal(c_after, (cycler(c=[1, 2, 3], lw=['r', 'g', 'b']) *
cycler('foo', [['y', 'g', 'blue'], ['b', 'k']])))

# Make sure that changing the key for a specific cycler
# doesn't break things for a composed cycler
c = (c1 + c2) * c3
c4 = cycler('bar', c3)
assert_equal(c, (cycler(c=[1, 2, 3], lw=['r', 'g', 'b']) *
cycler('foo', [['y', 'g', 'blue'], ['b', 'k']])))
assert_equal(c3, cycler('foo', [['y', 'g', 'blue'], ['b', 'k']]))


def _eq_test_helper(a, b, res):
if res:
assert_equal(a, b)
Expand Down