Skip to content

Commit cd0e03c

Browse files
committed
Merge pull request #14 from WeatherGod/expand_cycler
ENH/API: Expand cycler() signature
2 parents ddc6e11 + cacb87f commit cd0e03c

File tree

3 files changed

+146
-61
lines changed

3 files changed

+146
-61
lines changed

cycler.py

Lines changed: 75 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
You can add cyclers::
88
99
from cycler import cycler
10-
cc = (cycler('color', list('rgb')) +
11-
cycler('linestyle', ['-', '--', '-.']))
10+
cc = (cycler(color=list('rgb')) +
11+
cycler(linestyle=['-', '--', '-.']))
1212
for d in cc:
1313
print(d)
1414
@@ -22,8 +22,8 @@
2222
You can multiply cyclers::
2323
2424
from cycler import cycler
25-
cc = (cycler('color', list('rgb')) *
26-
cycler('linestyle', ['-', '--', '-.']))
25+
cc = (cycler(color=list('rgb')) *
26+
cycler(linestyle=['-', '--', '-.']))
2727
for d in cc:
2828
print(d)
2929
@@ -113,8 +113,8 @@ def __init__(self, left, right=None, op=None):
113113
Do not use this directly, use `cycler` function instead.
114114
"""
115115
self._keys = _process_keys(left, right)
116-
self._left = copy.copy(left)
117-
self._right = copy.copy(right)
116+
self._left = copy.deepcopy(left)
117+
self._right = copy.deepcopy(right)
118118
self._op = op
119119

120120
@property
@@ -164,7 +164,7 @@ def __getitem__(self, key):
164164
# TODO : maybe add numpy style fancy slicing
165165
if isinstance(key, slice):
166166
trans = self._transpose()
167-
return reduce(add, (cycler(k, v[key])
167+
return reduce(add, (_cycler(k, v[key])
168168
for k, v in six.iteritems(trans)))
169169
else:
170170
raise ValueError("Can only use slices with Cycler.__getitem__")
@@ -203,7 +203,7 @@ def __mul__(self, other):
203203
return Cycler(self, other, product)
204204
elif isinstance(other, int):
205205
trans = self._transpose()
206-
return reduce(add, (cycler(k, v*other)
206+
return reduce(add, (_cycler(k, v*other)
207207
for k, v in six.iteritems(trans)))
208208
else:
209209
return NotImplemented
@@ -228,11 +228,11 @@ def __iadd__(self, other):
228228
other : Cycler
229229
The second Cycler
230230
"""
231-
old_self = copy.copy(self)
231+
old_self = copy.deepcopy(self)
232232
self._keys = _process_keys(old_self, other)
233233
self._left = old_self
234234
self._op = zip
235-
self._right = copy.copy(other)
235+
self._right = copy.deepcopy(other)
236236
return self
237237

238238
def __imul__(self, other):
@@ -245,11 +245,11 @@ def __imul__(self, other):
245245
The second Cycler
246246
"""
247247

248-
old_self = copy.copy(self)
248+
old_self = copy.deepcopy(self)
249249
self._keys = _process_keys(old_self, other)
250250
self._left = old_self
251251
self._op = product
252-
self._right = copy.copy(other)
252+
self._right = copy.deepcopy(other)
253253
return self
254254

255255
def __eq__(self, other):
@@ -329,17 +329,76 @@ def simplify(self):
329329
# I would believe that there is some performance implications
330330

331331
trans = self._transpose()
332-
return reduce(add, (cycler(k, v) for k, v in six.iteritems(trans)))
332+
return reduce(add, (_cycler(k, v) for k, v in six.iteritems(trans)))
333333

334334

335-
def cycler(label, itr):
335+
def cycler(*args, **kwargs):
336+
"""
337+
Create a new `Cycler` object from a single positional argument,
338+
a pair of positional arguments, or the combination of keyword arguments.
339+
340+
cycler(arg)
341+
cycler(label1=itr1[, label2=iter2[, ...]])
342+
cycler(label, itr)
343+
344+
Form 1 simply copies a given `Cycler` object.
345+
346+
Form 2 composes a `Cycler` as an inner product of the
347+
pairs of keyword arguments. In other words, all of the
348+
iterables are cycled simultaneously, as if through zip().
349+
350+
Form 3 creates a `Cycler` from a label and an iterable.
351+
This is useful for when the label cannot be a keyword argument
352+
(e.g., an integer or a name that has a space in it).
353+
354+
Parameters
355+
----------
356+
arg : Cycler
357+
Copy constructor for Cycler.
358+
359+
label : name
360+
The property key. In the 2-arg form of the function,
361+
the label can be any hashable object. In the keyword argument
362+
form of the function, it must be a valid python identifier.
363+
364+
itr : iterable
365+
Finite length iterable of the property values.
366+
367+
Returns
368+
-------
369+
cycler : Cycler
370+
New `Cycler` for the given property
371+
372+
"""
373+
if args and kwargs:
374+
raise TypeError("cyl() can only accept positional OR keyword "
375+
"arguments -- not both.")
376+
377+
if len(args) == 1:
378+
if not isinstance(args[0], Cycler):
379+
raise TypeError("If only one positional argument given, it must "
380+
" be a Cycler instance.")
381+
return copy.deepcopy(args[0])
382+
elif len(args) == 2:
383+
return _cycler(*args)
384+
elif len(args) > 2:
385+
raise TypeError("Only a single Cycler can be accepted as the lone "
386+
"positional argument. Use keyword arguments instead.")
387+
388+
if kwargs:
389+
return reduce(add, (_cycler(k, v) for k, v in six.iteritems(kwargs)))
390+
391+
raise TypeError("Must have at least a positional OR keyword arguments")
392+
393+
394+
def _cycler(label, itr):
336395
"""
337396
Create a new `Cycler` object from a property name and
338397
iterable of values.
339398
340399
Parameters
341400
----------
342-
label : str
401+
label : hashable
343402
The property key.
344403
345404
itr : iterable
@@ -357,7 +416,7 @@ def cycler(label, itr):
357416
raise ValueError(msg)
358417

359418
if label in keys:
360-
return copy.copy(itr)
419+
return copy.deepcopy(itr)
361420
else:
362421
lab = keys.pop()
363422
itr = list(v[lab] for v in itr)

doc/source/index.rst

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ hashable (as it will eventually be used as the key in a :obj:`dict`).
4242
from cycler import cycler
4343
4444
45-
color_cycle = cycler('color', ['r', 'g', 'b'])
45+
color_cycle = cycler(color=['r', 'g', 'b'])
4646
color_cycle
4747
4848
The `Cycler` knows it's length and keys:
@@ -61,12 +61,12 @@ the label
6161
for v in color_cycle:
6262
print(v)
6363
64-
`Cycler` objects can be passed as the second argument to :func:`cycler`
64+
`Cycler` objects can be passed as the argument to :func:`cycler`
6565
which returns a new `Cycler` with a new label, but the same values.
6666

6767
.. ipython:: python
6868
69-
cycler('ec', color_cycle)
69+
cycler(ec=color_cycle)
7070
7171
7272
Iterating over a `Cycler` results in the finite list of entries, to
@@ -94,12 +94,12 @@ Equal length `Cycler` s with different keys can be added to get the
9494

9595
.. ipython:: python
9696
97-
lw_cycle = cycler('lw', range(1, 4))
97+
lw_cycle = cycler(lw=range(1, 4))
9898
9999
wc = lw_cycle + color_cycle
100100
101101
The result has the same length and has keys which are the union of the
102-
two input `Cycler` s.
102+
two input `Cycler`'s.
103103

104104
.. ipython:: python
105105
@@ -123,6 +123,17 @@ As with arithmetic, addition is commutative
123123
for j, (a, b) in enumerate(zip(lw_c, c_lw)):
124124
print('({j}) A: {A!r} B: {B!r}'.format(j=j, A=a, B=b))
125125
126+
For convenience, the :func:`cycler` function can have multiple
127+
key-value pairs and will automatically compose them into a single
128+
`Cycler` via addition
129+
130+
.. ipython:: python
131+
132+
wc = cycler(c=['r', 'g', 'b'], lw=range(3))
133+
134+
for s in wc:
135+
print(s)
136+
126137
127138
Multiplication
128139
~~~~~~~~~~~~~~
@@ -131,7 +142,7 @@ Any pair of `Cycler` can be multiplied
131142

132143
.. ipython:: python
133144
134-
m_cycle = cycler('marker', ['s', 'o'])
145+
m_cycle = cycler(marker=['s', 'o'])
135146
136147
m_c = m_cycle * color_cycle
137148
@@ -199,7 +210,7 @@ We can use `Cycler` instances to cycle over one or more ``kwarg`` to
199210
figsize=(8, 4))
200211
x = np.arange(10)
201212

202-
color_cycle = cycler('c', ['r', 'g', 'b'])
213+
color_cycle = cycler(c=['r', 'g', 'b'])
203214

204215
for i, sty in enumerate(color_cycle):
205216
ax1.plot(x, x*(i+1), **sty)
@@ -219,7 +230,7 @@ We can use `Cycler` instances to cycle over one or more ``kwarg`` to
219230
figsize=(8, 4))
220231
x = np.arange(10)
221232

222-
color_cycle = cycler('c', ['r', 'g', 'b'])
233+
color_cycle = cycler(c=['r', 'g', 'b'])
223234
ls_cycle = cycler('ls', ['-', '--'])
224235
lw_cycle = cycler('lw', range(1, 4))
225236

@@ -243,14 +254,14 @@ A :obj:`ValueError` is raised if unequal length `Cycler` s are added together
243254
.. ipython:: python
244255
:okexcept:
245256
246-
cycler('c', ['r', 'g', 'b']) + cycler('ls', ['-', '--'])
257+
cycler(c=['r', 'g', 'b']) + cycler(ls=['-', '--'])
247258
248259
or if two cycles which have overlapping keys are composed
249260

250261
.. ipython:: python
251262
:okexcept:
252263
253-
color_cycle = cycler('c', ['r', 'g', 'b'])
264+
color_cycle = cycler(c=['r', 'g', 'b'])
254265
255266
color_cycle + color_cycle
256267

0 commit comments

Comments
 (0)