43
43
44
44
from __future__ import annotations
45
45
46
- from collections .abc import Hashable , Iterable
46
+ from collections .abc import Hashable , Iterable , Generator
47
47
import copy
48
48
from functools import reduce
49
49
from itertools import product , cycle
50
50
from operator import mul , add
51
- from typing import TypeVar , Generic , Generator , Any , overload
51
+ # Dict, List, Union required for runtime cast calls
52
+ from typing import TypeVar , Generic , Callable , Union , Dict , List , Any , overload , cast
52
53
53
54
__version__ = "0.12.0.dev0"
54
55
55
56
K = TypeVar ("K" , bound = Hashable )
57
+ L = TypeVar ("L" , bound = Hashable )
56
58
V = TypeVar ("V" )
59
+ U = TypeVar ("U" )
57
60
58
61
59
62
def _process_keys (
60
- left : Cycler [K , V ] | Iterable [dict [K , V ]] | None ,
63
+ left : Cycler [K , V ] | Iterable [dict [K , V ]],
61
64
right : Cycler [K , V ] | Iterable [dict [K , V ]] | None ,
62
65
) -> set [K ]:
63
66
"""
@@ -73,7 +76,7 @@ def _process_keys(
73
76
keys : set
74
77
The keys in the composition of the two cyclers.
75
78
"""
76
- l_peek : dict [K , V ] = next (iter (left )) if left is not None else {}
79
+ l_peek : dict [K , V ] = next (iter (left )) if left != [] else {}
77
80
r_peek : dict [K , V ] = next (iter (right )) if right is not None else {}
78
81
l_key : set [K ] = set (l_peek .keys ())
79
82
r_key : set [K ] = set (r_peek .keys ())
@@ -82,7 +85,7 @@ def _process_keys(
82
85
return l_key | r_key
83
86
84
87
85
- def concat (left : Cycler [K , V ], right : Cycler [K , V ]) -> Cycler [K , V ]:
88
+ def concat (left : Cycler [K , V ], right : Cycler [K , U ]) -> Cycler [K , V | U ]:
86
89
r"""
87
90
Concatenate `Cycler`\s, as if chained using `itertools.chain`.
88
91
@@ -108,8 +111,8 @@ def concat(left: Cycler[K, V], right: Cycler[K, V]) -> Cycler[K, V]:
108
111
both = left .keys & right .keys , just_one = left .keys ^ right .keys
109
112
)
110
113
)
111
- _l = left .by_key ()
112
- _r = right .by_key ()
114
+ _l = cast ( Dict [ K , List [ Union [ V , U ]]], left .by_key () )
115
+ _r = cast ( Dict [ K , List [ Union [ V , U ]]], right .by_key () )
113
116
return reduce (add , (_cycler (k , _l [k ] + _r [k ]) for k in left .keys ))
114
117
115
118
@@ -156,15 +159,15 @@ def __init__(
156
159
Do not use this directly, use `cycler` function instead.
157
160
"""
158
161
if isinstance (left , Cycler ):
159
- self ._left : Cycler [K , V ] | list [dict [K , V ]] | None = Cycler (
162
+ self ._left : Cycler [K , V ] | list [dict [K , V ]] = Cycler (
160
163
left ._left , left ._right , left ._op
161
164
)
162
165
elif left is not None :
163
166
# Need to copy the dictionary or else that will be a residual
164
167
# mutable that could lead to strange errors
165
168
self ._left = [copy .copy (v ) for v in left ]
166
169
else :
167
- self ._left = None
170
+ self ._left = []
168
171
169
172
if isinstance (right , Cycler ):
170
173
self ._right : Cycler [K , V ] | list [dict [K , V ]] | None = Cycler (
@@ -220,8 +223,6 @@ def change_key(self, old: K, new: K) -> None:
220
223
221
224
# self._left should always be non-None
222
225
# if self._keys is non-empty.
223
- elif self ._left is None :
224
- pass
225
226
elif isinstance (self ._left , Cycler ):
226
227
self ._left .change_key (old , new )
227
228
else :
@@ -264,10 +265,9 @@ def __getitem__(self, key: slice) -> Cycler[K, V]:
264
265
raise ValueError ("Can only use slices with Cycler.__getitem__" )
265
266
266
267
def __iter__ (self ) -> Generator [dict [K , V ], None , None ]:
267
- if self ._right is None or self ._left is None :
268
- if self ._left is not None :
269
- for left in self ._left :
270
- yield dict (left )
268
+ if self ._right is None :
269
+ for left in self ._left :
270
+ yield dict (left )
271
271
else :
272
272
if self ._op is None :
273
273
raise TypeError (
@@ -279,7 +279,7 @@ def __iter__(self) -> Generator[dict[K, V], None, None]:
279
279
out .update (b )
280
280
yield out
281
281
282
- def __add__ (self , other : Cycler [K , V ]) -> Cycler [K , V ]:
282
+ def __add__ (self , other : Cycler [L , U ]) -> Cycler [K | L , V | U ]:
283
283
"""
284
284
Pair-wise combine two equal length cyclers (zip).
285
285
@@ -291,9 +291,21 @@ def __add__(self, other: Cycler[K, V]) -> Cycler[K, V]:
291
291
raise ValueError (
292
292
f"Can only add equal length cycles, not { len (self )} and { len (other )} "
293
293
)
294
- return Cycler (self , other , zip )
294
+ return Cycler (
295
+ cast (Cycler [Union [K , L ], Union [V , U ]], self ),
296
+ cast (Cycler [Union [K , L ], Union [V , U ]], other ),
297
+ zip
298
+ )
299
+
300
+ @overload
301
+ def __mul__ (self , other : Cycler [L , U ]) -> Cycler [K | L , V | U ]:
302
+ ...
303
+
304
+ @overload
305
+ def __mul__ (self , other : int ) -> Cycler [K , V ]:
306
+ ...
295
307
296
- def __mul__ (self , other : Cycler [ K , V ] | int ) -> Cycler [ K , V ] :
308
+ def __mul__ (self , other ) :
297
309
"""
298
310
Outer product of two cyclers (`itertools.product`) or integer
299
311
multiplication.
@@ -303,7 +315,11 @@ def __mul__(self, other: Cycler[K, V] | int) -> Cycler[K, V]:
303
315
other : Cycler or int
304
316
"""
305
317
if isinstance (other , Cycler ):
306
- return Cycler (self , other , product )
318
+ return Cycler (
319
+ cast (Cycler [Union [K , L ], Union [V , U ]], self ),
320
+ cast (Cycler [Union [K , L ], Union [V , U ]], other ),
321
+ product
322
+ )
307
323
elif isinstance (other , int ):
308
324
trans = self .by_key ()
309
325
return reduce (
@@ -312,22 +328,28 @@ def __mul__(self, other: Cycler[K, V] | int) -> Cycler[K, V]:
312
328
else :
313
329
return NotImplemented
314
330
315
- def __rmul__ (self , other : Cycler [K , V ]) -> Cycler [K , V ]:
331
+ @overload
332
+ def __rmul__ (self , other : Cycler [L , U ]) -> Cycler [K | L , V | U ]:
333
+ ...
334
+
335
+ @overload
336
+ def __rmul__ (self , other : int ) -> Cycler [K , V ]:
337
+ ...
338
+
339
+ def __rmul__ (self , other ):
316
340
return self * other
317
341
318
342
def __len__ (self ) -> int :
319
- op_dict = {zip : min , product : mul }
320
- if self ._left is None :
321
- if self ._left is None :
322
- return 0
323
- return 0
343
+ op_dict : dict [Callable , Callable [[int , int ], int ]] = {zip : min , product : mul }
324
344
if self ._right is None :
325
345
return len (self ._left )
326
346
l_len = len (self ._left )
327
347
r_len = len (self ._right )
328
- return op_dict [self ._op ](l_len , r_len ) # type: ignore
348
+ return op_dict [self ._op ](l_len , r_len )
329
349
330
- def __iadd__ (self , other : Cycler [K , V ]) -> Cycler [K , V ]:
350
+ # iadd and imul do not exapand the the type as the returns must be consistent with
351
+ # self, thus they flag as inconsistent with add/mul
352
+ def __iadd__ (self , other : Cycler [K , V ]) -> Cycler [K , V ]: # type: ignore[misc]
331
353
"""
332
354
In-place pair-wise combine two equal length cyclers (zip).
333
355
@@ -345,7 +367,7 @@ def __iadd__(self, other: Cycler[K, V]) -> Cycler[K, V]:
345
367
self ._right = Cycler (other ._left , other ._right , other ._op )
346
368
return self
347
369
348
- def __imul__ (self , other : Cycler [K , V ] | int ) -> Cycler [K , V ]:
370
+ def __imul__ (self , other : Cycler [K , V ] | int ) -> Cycler [K , V ]: # type: ignore[misc]
349
371
"""
350
372
In-place outer product of two cyclers (`itertools.product`).
351
373
@@ -451,7 +473,7 @@ def simplify(self) -> Cycler[K, V]:
451
473
452
474
453
475
@overload
454
- def cycler (args : Cycler [K , V ]) -> Cycler [K , V ]:
476
+ def cycler (arg : Cycler [K , V ]) -> Cycler [K , V ]:
455
477
...
456
478
457
479
@@ -505,7 +527,7 @@ def cycler(*args, **kwargs):
505
527
"""
506
528
if args and kwargs :
507
529
raise TypeError (
508
- "cyl () can only accept positional OR keyword arguments -- not both."
530
+ "cycler () can only accept positional OR keyword arguments -- not both."
509
531
)
510
532
511
533
if len (args ) == 1 :
0 commit comments