Skip to content

Commit 723d82b

Browse files
authored
Merge pull request #61 from anntzer/cleanup
Cleanups
2 parents d786d66 + 0429ea2 commit 723d82b

File tree

1 file changed

+62
-122
lines changed

1 file changed

+62
-122
lines changed

cycler.py

Lines changed: 62 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -57,16 +57,17 @@
5757

5858
def _process_keys(left, right):
5959
"""
60-
Helper function to compose cycler keys
60+
Helper function to compose cycler keys.
6161
6262
Parameters
6363
----------
6464
left, right : iterable of dictionaries or None
65-
The cyclers to be composed
65+
The cyclers to be composed.
66+
6667
Returns
6768
-------
6869
keys : set
69-
The keys in the composition of the two cyclers
70+
The keys in the composition of the two cyclers.
7071
"""
7172
l_peek = next(iter(left)) if left is not None else {}
7273
r_peek = next(iter(right)) if right is not None else {}
@@ -77,9 +78,38 @@ def _process_keys(left, right):
7778
return l_key | r_key
7879

7980

81+
def concat(left, right):
82+
r"""
83+
Concatenate `Cycler`\s, as if chained using `itertools.chain`.
84+
85+
The keys must match exactly.
86+
87+
Examples
88+
--------
89+
>>> num = cycler('a', range(3))
90+
>>> let = cycler('a', 'abc')
91+
>>> num.concat(let)
92+
cycler('a', [0, 1, 2, 'a', 'b', 'c'])
93+
94+
Returns
95+
-------
96+
`Cycler`
97+
The concatenated cycler.
98+
"""
99+
if left.keys != right.keys:
100+
raise ValueError("Keys do not match:\n"
101+
"\tIntersection: {both!r}\n"
102+
"\tDisjoint: {just_one!r}".format(
103+
both=left.keys & right.keys,
104+
just_one=left.keys ^ right.keys))
105+
_l = left.by_key()
106+
_r = right.by_key()
107+
return reduce(add, (_cycler(k, _l[k] + _r[k]) for k in left.keys))
108+
109+
80110
class Cycler(object):
81111
"""
82-
Composable cycles
112+
Composable cycles.
83113
84114
This class has compositions methods:
85115
@@ -95,25 +125,22 @@ class Cycler(object):
95125
``*=``
96126
in-place ``*``
97127
98-
and supports basic slicing via ``[]``
128+
and supports basic slicing via ``[]``.
99129
100130
Parameters
101131
----------
102-
left : Cycler or None
103-
The 'left' cycler
104-
105-
right : Cycler or None
106-
The 'right' cycler
107-
132+
left, right : Cycler or None
133+
The 'left' and 'right' cyclers.
108134
op : func or None
109135
Function which composes the 'left' and 'right' cyclers.
110-
111136
"""
137+
112138
def __call__(self):
113139
return cycle(self)
114140

115141
def __init__(self, left, right=None, op=None):
116-
"""Semi-private init
142+
"""
143+
Semi-private init.
117144
118145
Do not use this directly, use `cycler` function instead.
119146
"""
@@ -143,9 +170,7 @@ def __contains__(self, k):
143170

144171
@property
145172
def keys(self):
146-
"""
147-
The keys this Cycler knows about
148-
"""
173+
"""The keys this Cycler knows about."""
149174
return set(self._keys)
150175

151176
def change_key(self, old, new):
@@ -156,7 +181,6 @@ def change_key(self, old, new):
156181
Does nothing if the old key is the same as the new key.
157182
Raises a ValueError if the new key is already a key.
158183
Raises a KeyError if the old key isn't a key.
159-
160184
"""
161185
if old == new:
162186
return
@@ -183,16 +207,6 @@ def change_key(self, old, new):
183207
# iteration.
184208
self._left = [{new: entry[old]} for entry in self._left]
185209

186-
def _compose(self):
187-
"""
188-
Compose the 'left' and 'right' components of this cycle
189-
"""
190-
for a, b in self._op(self._left, self._right):
191-
out = dict()
192-
out.update(a)
193-
out.update(b)
194-
yield out
195-
196210
@classmethod
197211
def _from_iter(cls, label, itr):
198212
"""
@@ -210,8 +224,8 @@ def _from_iter(cls, label, itr):
210224
211225
Returns
212226
-------
213-
cycler : Cycler
214-
New 'base' `Cycler`
227+
`Cycler`
228+
New 'base' cycler.
215229
"""
216230
ret = cls(None)
217231
ret._left = list({label: v} for v in itr)
@@ -228,18 +242,22 @@ def __getitem__(self, key):
228242

229243
def __iter__(self):
230244
if self._right is None:
231-
return iter(dict(l) for l in self._left)
232-
233-
return self._compose()
245+
for l in self._left:
246+
yield dict(l)
247+
else:
248+
for a, b in self._op(self._left, self._right):
249+
out = dict()
250+
out.update(a)
251+
out.update(b)
252+
yield out
234253

235254
def __add__(self, other):
236255
"""
237-
Pair-wise combine two equal length cycles (zip)
256+
Pair-wise combine two equal length cyclers (zip).
238257
239258
Parameters
240259
----------
241260
other : Cycler
242-
The second Cycler
243261
"""
244262
if len(self) != len(other):
245263
raise ValueError("Can only add equal length cycles, "
@@ -248,13 +266,12 @@ def __add__(self, other):
248266

249267
def __mul__(self, other):
250268
"""
251-
Outer product of two cycles (`itertools.product`) or integer
269+
Outer product of two cyclers (`itertools.product`) or integer
252270
multiplication.
253271
254272
Parameters
255273
----------
256274
other : Cycler or int
257-
The second Cycler or integer
258275
"""
259276
if isinstance(other, Cycler):
260277
return Cycler(self, other, product)
@@ -277,12 +294,11 @@ def __len__(self):
277294

278295
def __iadd__(self, other):
279296
"""
280-
In-place pair-wise combine two equal length cycles (zip)
297+
In-place pair-wise combine two equal length cyclers (zip).
281298
282299
Parameters
283300
----------
284301
other : Cycler
285-
The second Cycler
286302
"""
287303
if not isinstance(other, Cycler):
288304
raise TypeError("Cannot += with a non-Cycler object")
@@ -296,12 +312,11 @@ def __iadd__(self, other):
296312

297313
def __imul__(self, other):
298314
"""
299-
In-place outer product of two cycles (`itertools.product`)
315+
In-place outer product of two cyclers (`itertools.product`).
300316
301317
Parameters
302318
----------
303319
other : Cycler
304-
The second Cycler
305320
"""
306321
if not isinstance(other, Cycler):
307322
raise TypeError("Cannot *= with a non-Cycler object")
@@ -314,14 +329,10 @@ def __imul__(self, other):
314329
return self
315330

316331
def __eq__(self, other):
317-
"""
318-
Check equality
319-
"""
320332
if len(self) != len(other):
321333
return False
322334
if self.keys ^ other.keys:
323335
return False
324-
325336
return all(a == b for a, b in zip(self, other))
326337

327338
def __ne__(self, other):
@@ -355,7 +366,8 @@ def _repr_html_(self):
355366
return output
356367

357368
def by_key(self):
358-
"""Values by key
369+
"""
370+
Values by key.
359371
360372
This returns the transposed values of the cycler. Iterating
361373
over a `Cycler` yields dicts with a single value for each key,
@@ -386,90 +398,22 @@ def by_key(self):
386398
_transpose = by_key
387399

388400
def simplify(self):
389-
"""Simplify the Cycler
390-
391-
Returned as a composition using only sums (no multiplications)
401+
"""
402+
Simplify the cycler into a sum (but no products) of cyclers.
392403
393404
Returns
394405
-------
395406
simple : Cycler
396-
An equivalent cycler using only summation"""
407+
"""
397408
# TODO: sort out if it is worth the effort to make sure this is
398409
# balanced. Currently it is is
399410
# (((a + b) + c) + d) vs
400411
# ((a + b) + (c + d))
401412
# I would believe that there is some performance implications
402-
403413
trans = self.by_key()
404414
return reduce(add, (_cycler(k, v) for k, v in trans.items()))
405415

406-
def concat(self, other):
407-
"""Concatenate this cycler and an other.
408-
409-
The keys must match exactly.
410-
411-
This returns a single Cycler which is equivalent to
412-
`itertools.chain(self, other)`
413-
414-
Examples
415-
--------
416-
417-
>>> num = cycler('a', range(3))
418-
>>> let = cycler('a', 'abc')
419-
>>> num.concat(let)
420-
cycler('a', [0, 1, 2, 'a', 'b', 'c'])
421-
422-
Parameters
423-
----------
424-
other : `Cycler`
425-
The `Cycler` to concatenate to this one.
426-
427-
Returns
428-
-------
429-
ret : `Cycler`
430-
The concatenated `Cycler`
431-
"""
432-
return concat(self, other)
433-
434-
435-
def concat(left, right):
436-
"""Concatenate two cyclers.
437-
438-
The keys must match exactly.
439-
440-
This returns a single Cycler which is equivalent to
441-
`itertools.chain(left, right)`
442-
443-
Examples
444-
--------
445-
446-
>>> num = cycler('a', range(3))
447-
>>> let = cycler('a', 'abc')
448-
>>> num.concat(let)
449-
cycler('a', [0, 1, 2, 'a', 'b', 'c'])
450-
451-
Parameters
452-
----------
453-
left, right : `Cycler`
454-
The two `Cycler` instances to concatenate
455-
456-
Returns
457-
-------
458-
ret : `Cycler`
459-
The concatenated `Cycler`
460-
"""
461-
if left.keys != right.keys:
462-
msg = '\n\t'.join(["Keys do not match:",
463-
"Intersection: {both!r}",
464-
"Disjoint: {just_one!r}"]).format(
465-
both=left.keys & right.keys,
466-
just_one=left.keys ^ right.keys)
467-
468-
raise ValueError(msg)
469-
470-
_l = left.by_key()
471-
_r = right.by_key()
472-
return reduce(add, (_cycler(k, _l[k] + _r[k]) for k in left.keys))
416+
concat = concat
473417

474418

475419
def cycler(*args, **kwargs):
@@ -495,12 +439,10 @@ def cycler(*args, **kwargs):
495439
----------
496440
arg : Cycler
497441
Copy constructor for Cycler (does a shallow copy of iterables).
498-
499442
label : name
500443
The property key. In the 2-arg form of the function,
501444
the label can be any hashable object. In the keyword argument
502445
form of the function, it must be a valid python identifier.
503-
504446
itr : iterable
505447
Finite length iterable of the property values.
506448
Can be a single-property `Cycler` that would
@@ -535,14 +477,12 @@ def cycler(*args, **kwargs):
535477

536478
def _cycler(label, itr):
537479
"""
538-
Create a new `Cycler` object from a property name and
539-
iterable of values.
480+
Create a new `Cycler` object from a property name and iterable of values.
540481
541482
Parameters
542483
----------
543484
label : hashable
544485
The property key.
545-
546486
itr : iterable
547487
Finite length iterable of the property values.
548488

0 commit comments

Comments
 (0)