@@ -58,15 +58,17 @@ def _process_keys(left, right):
5858
5959 Parameters
6060 ----------
61- left, right : Cycler or None
61+ left, right : iterable of dictionaries or None
6262 The cyclers to be composed
6363 Returns
6464 -------
6565 keys : set
6666 The keys in the composition of the two cyclers
6767 """
68- l_key = left .keys if left is not None else set ()
69- r_key = right .keys if right is not None else set ()
68+ l_peek = next (iter (left )) if left is not None else {}
69+ r_peek = next (iter (right )) if right is not None else {}
70+ l_key = set (l_peek .keys ())
71+ r_key = set (r_peek .keys ())
7072 if l_key & r_key :
7173 raise ValueError ("Can not compose overlapping cycles" )
7274 return l_key | r_key
@@ -112,9 +114,21 @@ def __init__(self, left, right=None, op=None):
112114
113115 Do not use this directly, use `cycler` function instead.
114116 """
115- self ._keys = _process_keys (left , right )
116- self ._left = copy .deepcopy (left )
117- self ._right = copy .deepcopy (right )
117+ if isinstance (left , Cycler ):
118+ self ._left = Cycler (left ._left , left ._right , left ._op )
119+ elif left is not None :
120+ self ._left = list (left )
121+ else :
122+ self ._left = None
123+
124+ if isinstance (right , Cycler ):
125+ self ._right = Cycler (right ._left , right ._right , right ._op )
126+ elif right is not None :
127+ self ._right = list (right )
128+ else :
129+ self ._right = None
130+
131+ self ._keys = _process_keys (self ._left , self ._right )
118132 self ._op = op
119133
120134 @property
@@ -228,11 +242,14 @@ def __iadd__(self, other):
228242 other : Cycler
229243 The second Cycler
230244 """
231- old_self = copy .deepcopy (self )
245+ if not isinstance (other , Cycler ):
246+ raise TypeError ("Cannot += with a non-Cycler object" )
247+ # True shallow copy of self is fine since this is in-place
248+ old_self = copy .copy (self )
232249 self ._keys = _process_keys (old_self , other )
233250 self ._left = old_self
234251 self ._op = zip
235- self ._right = copy . deepcopy (other )
252+ self ._right = Cycler (other . _left , other . _right , other . _op )
236253 return self
237254
238255 def __imul__ (self , other ):
@@ -244,12 +261,14 @@ def __imul__(self, other):
244261 other : Cycler
245262 The second Cycler
246263 """
247-
248- old_self = copy .deepcopy (self )
264+ if not isinstance (other , Cycler ):
265+ raise TypeError ("Cannot *= with a non-Cycler object" )
266+ # True shallow copy of self is fine since this is in-place
267+ old_self = copy .copy (self )
249268 self ._keys = _process_keys (old_self , other )
250269 self ._left = old_self
251270 self ._op = product
252- self ._right = copy . deepcopy (other )
271+ self ._right = Cycler (other . _left , other . _right , other . _op )
253272 return self
254273
255274 def __eq__ (self , other ):
@@ -354,7 +373,7 @@ def cycler(*args, **kwargs):
354373 Parameters
355374 ----------
356375 arg : Cycler
357- Copy constructor for Cycler.
376+ Copy constructor for Cycler (does a shallow copy of iterables) .
358377
359378 label : name
360379 The property key. In the 2-arg form of the function,
@@ -363,6 +382,8 @@ def cycler(*args, **kwargs):
363382
364383 itr : iterable
365384 Finite length iterable of the property values.
385+ Can be a single-property `Cycler` that would
386+ be like a key change, but as a shallow copy.
366387
367388 Returns
368389 -------
@@ -378,7 +399,7 @@ def cycler(*args, **kwargs):
378399 if not isinstance (args [0 ], Cycler ):
379400 raise TypeError ("If only one positional argument given, it must "
380401 " be a Cycler instance." )
381- return copy . deepcopy (args [0 ])
402+ return Cycler (args [0 ])
382403 elif len (args ) == 2 :
383404 return _cycler (* args )
384405 elif len (args ) > 2 :
@@ -415,10 +436,9 @@ def _cycler(label, itr):
415436 msg = "Can not create Cycler from a multi-property Cycler"
416437 raise ValueError (msg )
417438
418- if label in keys :
419- return copy .deepcopy (itr )
420- else :
421- lab = keys .pop ()
422- itr = list (v [lab ] for v in itr )
439+ lab = keys .pop ()
440+ # Doesn't need to be a new list because
441+ # _from_iter() will be creating that new list anyway.
442+ itr = (v [lab ] for v in itr )
423443
424444 return Cycler ._from_iter (label , itr )
0 commit comments