57
57
58
58
def _process_keys (left , right ):
59
59
"""
60
- Helper function to compose cycler keys
60
+ Helper function to compose cycler keys.
61
61
62
62
Parameters
63
63
----------
64
64
left, right : iterable of dictionaries or None
65
- The cyclers to be composed
65
+ The cyclers to be composed.
66
+
66
67
Returns
67
68
-------
68
69
keys : set
69
- The keys in the composition of the two cyclers
70
+ The keys in the composition of the two cyclers.
70
71
"""
71
72
l_peek = next (iter (left )) if left is not None else {}
72
73
r_peek = next (iter (right )) if right is not None else {}
@@ -77,9 +78,38 @@ def _process_keys(left, right):
77
78
return l_key | r_key
78
79
79
80
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
+ "\t Intersection: {both!r}\n "
102
+ "\t Disjoint: {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
+
80
110
class Cycler (object ):
81
111
"""
82
- Composable cycles
112
+ Composable cycles.
83
113
84
114
This class has compositions methods:
85
115
@@ -95,25 +125,22 @@ class Cycler(object):
95
125
``*=``
96
126
in-place ``*``
97
127
98
- and supports basic slicing via ``[]``
128
+ and supports basic slicing via ``[]``.
99
129
100
130
Parameters
101
131
----------
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.
108
134
op : func or None
109
135
Function which composes the 'left' and 'right' cyclers.
110
-
111
136
"""
137
+
112
138
def __call__ (self ):
113
139
return cycle (self )
114
140
115
141
def __init__ (self , left , right = None , op = None ):
116
- """Semi-private init
142
+ """
143
+ Semi-private init.
117
144
118
145
Do not use this directly, use `cycler` function instead.
119
146
"""
@@ -143,9 +170,7 @@ def __contains__(self, k):
143
170
144
171
@property
145
172
def keys (self ):
146
- """
147
- The keys this Cycler knows about
148
- """
173
+ """The keys this Cycler knows about."""
149
174
return set (self ._keys )
150
175
151
176
def change_key (self , old , new ):
@@ -156,7 +181,6 @@ def change_key(self, old, new):
156
181
Does nothing if the old key is the same as the new key.
157
182
Raises a ValueError if the new key is already a key.
158
183
Raises a KeyError if the old key isn't a key.
159
-
160
184
"""
161
185
if old == new :
162
186
return
@@ -183,16 +207,6 @@ def change_key(self, old, new):
183
207
# iteration.
184
208
self ._left = [{new : entry [old ]} for entry in self ._left ]
185
209
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
-
196
210
@classmethod
197
211
def _from_iter (cls , label , itr ):
198
212
"""
@@ -210,8 +224,8 @@ def _from_iter(cls, label, itr):
210
224
211
225
Returns
212
226
-------
213
- cycler : Cycler
214
- New 'base' `Cycler`
227
+ ` Cycler`
228
+ New 'base' cycler.
215
229
"""
216
230
ret = cls (None )
217
231
ret ._left = list ({label : v } for v in itr )
@@ -228,18 +242,22 @@ def __getitem__(self, key):
228
242
229
243
def __iter__ (self ):
230
244
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
234
253
235
254
def __add__ (self , other ):
236
255
"""
237
- Pair-wise combine two equal length cycles (zip)
256
+ Pair-wise combine two equal length cyclers (zip).
238
257
239
258
Parameters
240
259
----------
241
260
other : Cycler
242
- The second Cycler
243
261
"""
244
262
if len (self ) != len (other ):
245
263
raise ValueError ("Can only add equal length cycles, "
@@ -248,13 +266,12 @@ def __add__(self, other):
248
266
249
267
def __mul__ (self , other ):
250
268
"""
251
- Outer product of two cycles (`itertools.product`) or integer
269
+ Outer product of two cyclers (`itertools.product`) or integer
252
270
multiplication.
253
271
254
272
Parameters
255
273
----------
256
274
other : Cycler or int
257
- The second Cycler or integer
258
275
"""
259
276
if isinstance (other , Cycler ):
260
277
return Cycler (self , other , product )
@@ -277,12 +294,11 @@ def __len__(self):
277
294
278
295
def __iadd__ (self , other ):
279
296
"""
280
- In-place pair-wise combine two equal length cycles (zip)
297
+ In-place pair-wise combine two equal length cyclers (zip).
281
298
282
299
Parameters
283
300
----------
284
301
other : Cycler
285
- The second Cycler
286
302
"""
287
303
if not isinstance (other , Cycler ):
288
304
raise TypeError ("Cannot += with a non-Cycler object" )
@@ -296,12 +312,11 @@ def __iadd__(self, other):
296
312
297
313
def __imul__ (self , other ):
298
314
"""
299
- In-place outer product of two cycles (`itertools.product`)
315
+ In-place outer product of two cyclers (`itertools.product`).
300
316
301
317
Parameters
302
318
----------
303
319
other : Cycler
304
- The second Cycler
305
320
"""
306
321
if not isinstance (other , Cycler ):
307
322
raise TypeError ("Cannot *= with a non-Cycler object" )
@@ -314,14 +329,10 @@ def __imul__(self, other):
314
329
return self
315
330
316
331
def __eq__ (self , other ):
317
- """
318
- Check equality
319
- """
320
332
if len (self ) != len (other ):
321
333
return False
322
334
if self .keys ^ other .keys :
323
335
return False
324
-
325
336
return all (a == b for a , b in zip (self , other ))
326
337
327
338
def __ne__ (self , other ):
@@ -355,7 +366,8 @@ def _repr_html_(self):
355
366
return output
356
367
357
368
def by_key (self ):
358
- """Values by key
369
+ """
370
+ Values by key.
359
371
360
372
This returns the transposed values of the cycler. Iterating
361
373
over a `Cycler` yields dicts with a single value for each key,
@@ -386,90 +398,22 @@ def by_key(self):
386
398
_transpose = by_key
387
399
388
400
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.
392
403
393
404
Returns
394
405
-------
395
406
simple : Cycler
396
- An equivalent cycler using only summation """
407
+ """
397
408
# TODO: sort out if it is worth the effort to make sure this is
398
409
# balanced. Currently it is is
399
410
# (((a + b) + c) + d) vs
400
411
# ((a + b) + (c + d))
401
412
# I would believe that there is some performance implications
402
-
403
413
trans = self .by_key ()
404
414
return reduce (add , (_cycler (k , v ) for k , v in trans .items ()))
405
415
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
473
417
474
418
475
419
def cycler (* args , ** kwargs ):
@@ -495,12 +439,10 @@ def cycler(*args, **kwargs):
495
439
----------
496
440
arg : Cycler
497
441
Copy constructor for Cycler (does a shallow copy of iterables).
498
-
499
442
label : name
500
443
The property key. In the 2-arg form of the function,
501
444
the label can be any hashable object. In the keyword argument
502
445
form of the function, it must be a valid python identifier.
503
-
504
446
itr : iterable
505
447
Finite length iterable of the property values.
506
448
Can be a single-property `Cycler` that would
@@ -535,14 +477,12 @@ def cycler(*args, **kwargs):
535
477
536
478
def _cycler (label , itr ):
537
479
"""
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.
540
481
541
482
Parameters
542
483
----------
543
484
label : hashable
544
485
The property key.
545
-
546
486
itr : iterable
547
487
Finite length iterable of the property values.
548
488
0 commit comments