python-iterutils 0.2.10.1__py3-none-any.whl → 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
iterutils/__init__.py CHANGED
@@ -2,1446 +2,8 @@
2
2
  # encoding: utf-8
3
3
 
4
4
  __author__ = "ChenyangGao <https://chenyanggao.github.io>"
5
- __version__ = (0, 2, 10)
6
- __all__ = [
7
- "Yield", "YieldFrom", "GenStep", "GenStepIter", "iterable",
8
- "async_iterable", "run_gen_step", "run_gen_step_iter",
9
- "as_gen_step", "as_gen_step_iter", "split_cm", "with_iter_next",
10
- "map", "filter", "reduce", "zip", "chain", "chain_from_iterable",
11
- "chunked", "foreach", "async_foreach", "through", "async_through",
12
- "flatten", "async_flatten", "collect", "async_collect",
13
- "group_collect", "async_group_collect", "iter_unique",
14
- "async_iter_unique", "wrap_iter", "wrap_aiter", "peek_iter", "peek_aiter",
15
- "acc_step", "cut_iter", "bfs_gen", "context", "backgroud_loop",
16
- ]
17
-
18
- from asyncio import create_task, sleep as async_sleep
19
- from builtins import map as _map, filter as _filter, zip as _zip
20
- from collections import defaultdict, deque
21
- from collections.abc import (
22
- AsyncIterable, AsyncIterator, Awaitable, Buffer, Callable,
23
- Collection, Container, Coroutine, Generator, Iterable, Iterator,
24
- Mapping, MutableMapping, MutableSet, MutableSequence, Sequence,
25
- ValuesView,
26
- )
27
- from contextlib import (
28
- asynccontextmanager, contextmanager, ExitStack, AsyncExitStack,
29
- AbstractContextManager, AbstractAsyncContextManager,
30
- )
31
- from copy import copy
32
- from dataclasses import dataclass
33
- from itertools import batched, chain as _chain, pairwise
34
- from inspect import iscoroutinefunction, signature
35
- from sys import _getframe, exc_info
36
- from _thread import start_new_thread
37
- from time import sleep, time
38
- from types import FrameType
39
- from typing import (
40
- cast, overload, Any, AsyncContextManager, ContextManager, Literal,
41
- )
42
-
43
- from asynctools import (
44
- async_filter, async_map, async_reduce, async_zip, async_batched,
45
- ensure_async, ensure_aiter, async_chain, collect as async_collect,
46
- )
47
- from texttools import format_time
48
- from undefined import undefined
49
-
50
-
51
- @dataclass(slots=True, frozen=True, unsafe_hash=True)
52
- class Yield:
53
- """专供 `run_gen_step_iter`,说明值需要 yield 给用户
54
- """
55
- value: Any
56
-
57
-
58
- @dataclass(slots=True, frozen=True, unsafe_hash=True)
59
- class YieldFrom:
60
- """专供 `run_gen_step_iter`,说明值需要解包后逐个 yield 给用户
61
- """
62
- value: Any
63
-
64
-
65
- @dataclass(slots=True, frozen=True, unsafe_hash=True)
66
- class GenStep:
67
- """专供 `run_gen_step` 和 `run_gen_step_iter`,说明值需要进一步提取
68
- """
69
- value: Generator
70
- max_depth: int = 0
71
-
72
-
73
- @dataclass(slots=True, frozen=True, unsafe_hash=True)
74
- class GenStepIter:
75
- """专供 `run_gen_step_iter`,说明值需要进一步提取
76
- """
77
- value: Generator
78
- max_depth: int = 0
79
-
80
-
81
- iterable = Iterable.__instancecheck__
82
- async_iterable = AsyncIterable.__instancecheck__
83
- isawaitable = Awaitable.__instancecheck__
84
-
85
-
86
- def _coalesce(vals, default=None):
87
- for val in vals:
88
- if val is not None:
89
- return val
90
- return default
91
-
92
-
93
- @overload
94
- def _get_async(back: int = 2, /, *, default: Literal[False] = False) -> bool:
95
- ...
96
- @overload
97
- def _get_async[T](back: int = 2, /, *, default: T) -> bool | T:
98
- ...
99
- def _get_async[T](back: int = 2, /, *, default: Literal[False] | T = False) -> bool | T:
100
- """往上查找,从最近的调用栈的命名空间中获取 `async_` 的值
101
- """
102
- def iter_frams(f: None | FrameType = _getframe(back)):
103
- while f:
104
- yield f.f_locals.get("async_")
105
- f = f.f_back
106
- return _coalesce(iter_frams(), default)
107
-
108
-
109
- def _run_gen_step(
110
- gen: Generator,
111
- /,
112
- drive_generator: int = 0,
113
- ):
114
- send = gen.send
115
- throw = gen.throw
116
- dgen = drive_generator if drive_generator < 0 else drive_generator - 1
117
- try:
118
- value: Any = send(None)
119
- while True:
120
- try:
121
- if isinstance(value, GenStep):
122
- value = _run_gen_step(value.value, value.max_depth)
123
- elif drive_generator and isinstance(value, Generator):
124
- value = _run_gen_step(value, dgen)
125
- except BaseException as e:
126
- value = throw(e)
127
- else:
128
- value = send(value)
129
- except StopIteration as e:
130
- value = e.value
131
- if isinstance(value, GenStep):
132
- value = _run_gen_step(value.value, value.max_depth)
133
- elif drive_generator and isinstance(value, Generator):
134
- value = _run_gen_step(value, dgen)
135
- return value
136
- finally:
137
- gen.close()
138
-
139
-
140
- async def _run_gen_step_async(
141
- gen: Generator,
142
- /,
143
- drive_generator: int = 0,
144
- ):
145
- send = gen.send
146
- throw = gen.throw
147
- dgen = drive_generator if drive_generator < 0 else drive_generator - 1
148
- try:
149
- value: Any = send(None)
150
- while True:
151
- try:
152
- if isawaitable(value):
153
- value = await value
154
- elif isinstance(value, GenStep):
155
- value = await _run_gen_step_async(value.value, value.max_depth)
156
- elif drive_generator and isinstance(value, Generator):
157
- value = await _run_gen_step_async(value, dgen)
158
- except BaseException as e:
159
- value = throw(e)
160
- else:
161
- value = send(value)
162
- except StopIteration as e:
163
- value = e.value
164
- if isawaitable(value):
165
- value = await value
166
- elif isinstance(value, GenStep):
167
- value = await _run_gen_step_async(value.value, value.max_depth)
168
- elif drive_generator and isinstance(value, Generator):
169
- value = await _run_gen_step_async(value, dgen)
170
- return value
171
- finally:
172
- gen.close()
173
-
174
-
175
- def run_gen_step[**Args](
176
- gen_step: Generator | Callable[Args, Generator],
177
- async_: None | Literal[False, True] = None,
178
- /,
179
- *args: Args.args,
180
- **kwds: Args.kwargs,
181
- ):
182
- """驱动生成器运行,并返回其结果
183
- """
184
- if async_ is None:
185
- async_ = _get_async()
186
- if not isinstance(gen_step, Generator):
187
- params = signature(gen_step).parameters
188
- if ((param := params.get("async_")) and
189
- (param.kind in (param.POSITIONAL_OR_KEYWORD, param.KEYWORD_ONLY))
190
- ):
191
- kwds.setdefault("async_", async_)
192
- gen_step = gen_step(*args, **kwds)
193
- if async_:
194
- return _run_gen_step_async(gen_step)
195
- else:
196
- return _run_gen_step(gen_step)
197
-
198
-
199
- def _run_gen_step_iter(
200
- gen: Generator,
201
- /,
202
- drive_generator: int = 0,
203
- ) -> Iterator:
204
- send = gen.send
205
- throw = gen.throw
206
- dgen = drive_generator if drive_generator < 0 else drive_generator - 1
207
- try:
208
- value: Any = send(None)
209
- while True:
210
- try:
211
- val_type = type(value)
212
- if issubclass(val_type, (Yield, YieldFrom)):
213
- value = value.value
214
- if isinstance(value, GenStepIter):
215
- yield from _run_gen_step_iter(value.value, value.max_depth)
216
- else:
217
- if isinstance(value, GenStep):
218
- value = _run_gen_step(value.value, value.max_depth)
219
- elif drive_generator and isinstance(value, Generator):
220
- value = _run_gen_step(value, dgen)
221
- if val_type is Yield:
222
- yield value
223
- elif val_type is YieldFrom:
224
- yield from value
225
- except BaseException as e:
226
- value = throw(e)
227
- else:
228
- value = send(value)
229
- except StopIteration as e:
230
- value = e.value
231
- val_type = type(value)
232
- if issubclass(val_type, (Yield, YieldFrom)):
233
- value = value.value
234
- elif isinstance(value, GenStepIter):
235
- yield from _run_gen_step_iter(value.value, value.max_depth)
236
- elif isinstance(value, GenStep):
237
- value = _run_gen_step(value.value, value.max_depth)
238
- elif drive_generator and isinstance(value, Generator):
239
- value = _run_gen_step(value, dgen)
240
- if val_type is Yield:
241
- yield value
242
- elif val_type is YieldFrom:
243
- yield from value
244
- finally:
245
- gen.close()
246
-
247
-
248
- async def _run_gen_step_async_iter(
249
- gen: Generator,
250
- /,
251
- drive_generator: int = 0,
252
- ) -> AsyncIterator:
253
- send = gen.send
254
- throw = gen.throw
255
- dgen = drive_generator if drive_generator < 0 else drive_generator - 1
256
- try:
257
- value: Any = send(None)
258
- while True:
259
- try:
260
- val_type = type(value)
261
- if issubclass(val_type, (Yield, YieldFrom)):
262
- value = value.value
263
- if isawaitable(value):
264
- value = await value
265
- elif isinstance(value, GenStepIter):
266
- async for el in _run_gen_step_async_iter(value.value, value.max_depth):
267
- yield el
268
- elif isinstance(value, GenStep):
269
- value = await _run_gen_step_async(value.value, value.max_depth)
270
- elif drive_generator and isinstance(value, Generator):
271
- value = await _run_gen_step_async(value, dgen)
272
- if val_type is Yield:
273
- yield value
274
- elif val_type is YieldFrom:
275
- if isinstance(value, AsyncIterable):
276
- async for el in value:
277
- yield el
278
- else:
279
- for el in value:
280
- yield el
281
- except BaseException as e:
282
- value = throw(e)
283
- else:
284
- value = send(value)
285
- except StopIteration as e:
286
- value = e.value
287
- val_type = type(value)
288
- if issubclass(val_type, (Yield, YieldFrom)):
289
- value = value.value
290
- if isawaitable(value):
291
- value = await value
292
- if isinstance(value, GenStepIter):
293
- async for el in _run_gen_step_async_iter(value.value, value.max_depth):
294
- yield el
295
- else:
296
- if isinstance(value, GenStep):
297
- value = await _run_gen_step_async(value.value, value.max_depth)
298
- elif drive_generator and isinstance(value, Generator):
299
- value = await _run_gen_step_async(value, dgen)
300
- if val_type is Yield:
301
- yield value
302
- elif val_type is YieldFrom:
303
- if isinstance(value, AsyncIterable):
304
- async for el in value:
305
- yield el
306
- else:
307
- for el in value:
308
- yield el
309
- finally:
310
- gen.close()
311
-
312
-
313
- @overload
314
- def run_gen_step_iter[**Args](
315
- gen_step: Generator | Callable[Args, Generator],
316
- async_: None = None,
317
- /,
318
- *args: Args.args,
319
- **kwds: Args.kwargs,
320
- ) -> Iterator | AsyncIterator:
321
- ...
322
- @overload
323
- def run_gen_step_iter[**Args](
324
- gen_step: Generator | Callable[Args, Generator],
325
- async_: Literal[False] = False,
326
- /,
327
- *args: Args.args,
328
- **kwds: Args.kwargs,
329
- ) -> Iterator:
330
- ...
331
- @overload
332
- def run_gen_step_iter[**Args](
333
- gen_step: Generator | Callable[Args, Generator],
334
- async_: Literal[True],
335
- /,
336
- *args: Args.args,
337
- **kwds: Args.kwargs,
338
- ) -> AsyncIterator:
339
- ...
340
- def run_gen_step_iter[**Args](
341
- gen_step: Generator | Callable[Args, Generator],
342
- async_: None | Literal[False, True] = None,
343
- /,
344
- *args: Args.args,
345
- **kwds: Args.kwargs,
346
- ) -> Iterator | AsyncIterator:
347
- """驱动生成器运行,并从中返回可迭代而出的值
348
- """
349
- if async_ is None:
350
- async_ = _get_async()
351
- if not isinstance(gen_step, Generator):
352
- params = signature(gen_step).parameters
353
- if ((param := params.get("async_")) and
354
- (param.kind in (param.POSITIONAL_OR_KEYWORD, param.KEYWORD_ONLY))
355
- ):
356
- kwds.setdefault("async_", async_)
357
- gen_step = gen_step(*args, **kwds)
358
- if async_:
359
- return _run_gen_step_async_iter(gen_step)
360
- else:
361
- return _run_gen_step_iter(gen_step)
362
-
363
-
364
- def as_gen_step[**Args](
365
- gen_step: Callable[Args, Generator],
366
- /,
367
- ) -> Callable[Args, Any]:
368
- default_async = _get_async(default=None)
369
- def wrapper(*args: Args.args, **kwds: Args.kwargs):
370
- return run_gen_step(
371
- gen_step,
372
- _coalesce((
373
- kwds.pop("async_", None),
374
- _get_async(default=default_async),
375
- )),
376
- *args,
377
- **kwds,
378
- )
379
- return wrapper
380
-
381
-
382
- def as_gen_step_iter[**Args](
383
- gen_step: Callable[Args, Generator],
384
- /,
385
- ) -> Callable[Args, Iterable | AsyncIterable]:
386
- default_async = _get_async(default=None)
387
- def wrapper(*args: Args.args, **kwds: Args.kwargs):
388
- return run_gen_step_iter(
389
- gen_step,
390
- _coalesce((
391
- kwds.pop("async_", None),
392
- _get_async(default=default_async),
393
- )),
394
- *args,
395
- **kwds,
396
- )
397
- return wrapper
398
-
399
-
400
- @overload
401
- def split_cm[T](
402
- cm: AbstractContextManager[T],
403
- /,
404
- ) -> tuple[Callable[[], T], Callable[[], Any]]:
405
- ...
406
- @overload
407
- def split_cm[T](
408
- cm: AbstractAsyncContextManager[T],
409
- /,
410
- ) -> tuple[Callable[[], Coroutine[Any, Any, T]], Callable[[], Coroutine]]:
411
- ...
412
- def split_cm[T](
413
- cm: AbstractContextManager[T] | AbstractAsyncContextManager[T],
414
- /,
415
- ) -> (
416
- tuple[Callable[[], T], Callable[[], Any]] |
417
- tuple[Callable[[], Coroutine[Any, Any, T]], Callable[[], Coroutine]]
418
- ):
419
- """拆分上下文管理器,以供 `run_gen_step` 和 `run_gen_step_iter` 使用
420
-
421
- .. code:: python
422
-
423
- if async_:
424
- async def process():
425
- async with cm as obj:
426
- do_what_you_want()
427
- return process()
428
- else:
429
- with cm as obj:
430
- do_what_you_want()
431
-
432
- 大概相当于
433
-
434
- .. code:: python
435
-
436
- def gen_step():
437
- enter, exit = split_cm(cm)
438
- obj = yield enter()
439
- try:
440
- do_what_you_want()
441
- finally:
442
- yield exit()
443
-
444
- run_gen_step(gen_step, async_)
445
- """
446
- if isinstance(cm, AbstractAsyncContextManager):
447
- enter: Callable = cm.__aenter__
448
- exit: Callable = cm.__aexit__
449
- else:
450
- enter = cm.__enter__
451
- exit = cm.__exit__
452
- return enter, lambda: exit(*exc_info())
453
-
454
-
455
- @overload
456
- def with_iter_next[T](
457
- iterable: Iterable[T],
458
- /,
459
- ) -> ContextManager[Callable[[], T]]:
460
- ...
461
- @overload
462
- def with_iter_next[T](
463
- iterable: AsyncIterable[T],
464
- /,
465
- ) -> ContextManager[Callable[[], Awaitable[T]]]:
466
- ...
467
- @contextmanager
468
- def with_iter_next[T](
469
- iterable: Iterable[T] | AsyncIterable[T],
470
- /,
471
- ):
472
- """包装迭代器,以供 `run_gen_step` 和 `run_gen_step_iter` 使用
473
-
474
- .. code:: python
475
-
476
- if async_:
477
- async def process():
478
- async for e in iterable:
479
- do_what_you_want()
480
- return process()
481
- else:
482
- for e in iterable:
483
- do_what_you_want()
484
-
485
- 大概相当于
486
-
487
- .. code:: python
488
-
489
- def gen_step():
490
- with with_iter_next(iterable) as do_next:
491
- while True:
492
- e = yield do_next()
493
- do_what_you_want()
494
-
495
- run_gen_step(gen_step, async_)
496
- """
497
- if isinstance(iterable, AsyncIterable):
498
- try:
499
- yield aiter(iterable).__anext__
500
- except StopAsyncIteration:
501
- pass
502
- else:
503
- try:
504
- yield iter(iterable).__next__
505
- except StopIteration:
506
- pass
507
-
508
-
509
- def map(
510
- function: None | Callable,
511
- iterable: Iterable | AsyncIterable,
512
- /,
513
- *iterables: Iterable | AsyncIterable,
514
- threaded: bool = False,
515
- ):
516
- """
517
- """
518
- if (
519
- threaded or
520
- iscoroutinefunction(function) or
521
- isinstance(iterable, AsyncIterable) or
522
- any(isinstance(i, AsyncIterable) for i in iterables)
523
- ):
524
- if function is None:
525
- if iterables:
526
- return async_zip(iterable, *iterables, threaded=threaded)
527
- elif threaded:
528
- return ensure_aiter(iterable, threaded=threaded)
529
- else:
530
- return iterable
531
- return async_map(function, iterable, *iterables)
532
- if function is None:
533
- if iterables:
534
- return _zip(iterable, *iterables)
535
- else:
536
- return iterable
537
- return _map(function, iterable, *iterables)
538
-
539
-
540
- def filter(
541
- function: None | Callable,
542
- iterable: Iterable | AsyncIterable,
543
- /,
544
- threaded: bool = False,
545
- ):
546
- """
547
- """
548
- if threaded or iscoroutinefunction(function) or isinstance(iterable, AsyncIterable):
549
- return async_filter(function, iterable, threaded=threaded)
550
- return _filter(function, iterable)
551
-
552
-
553
- def reduce(
554
- function: Callable,
555
- iterable: Iterable | AsyncIterable,
556
- initial: Any = undefined,
557
- /,
558
- threaded: bool = False,
559
- ):
560
- """
561
- """
562
- if threaded or iscoroutinefunction(function) or isinstance(iterable, AsyncIterable):
563
- return async_reduce(function, iterable, initial, threaded=threaded)
564
- from functools import reduce
565
- if initial is undefined:
566
- return reduce(function, iterable)
567
- return reduce(function, iterable, initial)
568
-
569
-
570
- def zip(
571
- iterable: Iterable | AsyncIterable,
572
- /,
573
- *iterables: Iterable | AsyncIterable,
574
- threaded: bool = False,
575
- ):
576
- """
577
- """
578
- if (not threaded and
579
- isinstance(iterable, Iterable) and
580
- all(isinstance(it, Iterable) for it in iterables)
581
- ):
582
- return _zip(iterable, *iterables)
583
- return async_zip(iterable, *iterables, threaded=threaded)
584
-
585
-
586
- @overload
587
- def chain[T](
588
- iterable: Iterable[T],
589
- /,
590
- *iterables: Iterable[T],
591
- threaded: Literal[False] = False,
592
- ) -> Iterator[T]:
593
- ...
594
- @overload
595
- def chain[T](
596
- iterable: Iterable[T],
597
- /,
598
- *iterables: Iterable[T] | AsyncIterable[T],
599
- threaded: Literal[True],
600
- ) -> AsyncIterator[T]:
601
- ...
602
- @overload
603
- def chain[T](
604
- iterable: AsyncIterable[T],
605
- /,
606
- *iterables: Iterable[T] | AsyncIterable[T],
607
- threaded: Literal[False, True] = False,
608
- ) -> AsyncIterator[T]:
609
- ...
610
- def chain[T](
611
- iterable: Iterable[T] | AsyncIterable[T],
612
- /,
613
- *iterables: Iterable[T] | AsyncIterable[T],
614
- threaded: Literal[False, True] = False,
615
- ) -> Iterator[T] | AsyncIterator[T]:
616
- if (not threaded and
617
- isinstance(iterable, Iterable) and
618
- all(isinstance(it, Iterable) for it in iterables)
619
- ):
620
- return _chain(iterable, *iterables) # type: ignore
621
- return async_chain(iterable, *iterables, threaded=threaded)
622
-
623
-
624
- @overload
625
- def chain_from_iterable[T](
626
- iterables: Iterable[Iterable[T]],
627
- threaded: bool = False,
628
- *,
629
- async_: Literal[False] = False,
630
- ) -> Iterator[T]:
631
- ...
632
- @overload
633
- def chain_from_iterable[T](
634
- iterables: Iterable[Iterable[T] | AsyncIterable[T]] | AsyncIterable[Iterable[T] | AsyncIterable[T]],
635
- threaded: bool = False,
636
- *,
637
- async_: Literal[True],
638
- ) -> AsyncIterator[T]:
639
- ...
640
- def chain_from_iterable[T](
641
- iterables: Iterable[Iterable[T]] | Iterable[Iterable[T] | AsyncIterable[T]] | AsyncIterable[Iterable[T] | AsyncIterable[T]],
642
- threaded: bool = False,
643
- *,
644
- async_: Literal[False, True] = False,
645
- ) -> Iterator[T] | AsyncIterator[T]:
646
- if async_ or not isinstance(iterables, Iterable):
647
- if isinstance(iterables, Iterable):
648
- return async_chain.from_iterable(iterables, threaded=threaded)
649
- else:
650
- return async_chain.from_iterable(iterables, threaded=threaded)
651
- return _chain.from_iterable(iterables) # type: ignore
652
-
653
- setattr(chain, "from_iterable", chain_from_iterable)
654
-
655
-
656
- @overload
657
- def chunked[T](
658
- iterable: Iterable[T],
659
- n: int = 1,
660
- /,
661
- *,
662
- threaded: Literal[False] = False,
663
- ) -> Iterator[Sequence[T]]:
664
- ...
665
- @overload
666
- def chunked[T](
667
- iterable: Iterable[T],
668
- n: int = 1,
669
- /,
670
- *,
671
- threaded: Literal[True],
672
- ) -> Iterator[Sequence[T]]:
673
- ...
674
- @overload
675
- def chunked[T](
676
- iterable: AsyncIterable[T],
677
- n: int = 1,
678
- /,
679
- *,
680
- threaded: Literal[False, True] = False,
681
- ) -> AsyncIterator[Sequence[T]]:
682
- ...
683
- def chunked[T](
684
- iterable: Iterable[T] | AsyncIterable[T],
685
- n: int = 1,
686
- /,
687
- *,
688
- threaded: Literal[False, True] = False,
689
- ) -> Iterator[Sequence[T]] | AsyncIterator[Sequence[T]]:
690
- """
691
- """
692
- if n < 0:
693
- n = 1
694
- if isinstance(iterable, Sequence):
695
- if n == 1:
696
- return ((e,) for e in iterable)
697
- return (iterable[i:j] for i, j in pairwise(range(0, len(iterable)+n, n)))
698
- elif not threaded and isinstance(iterable, Iterable):
699
- return batched(iterable, n)
700
- else:
701
- return async_batched(iterable, n, threaded=threaded)
702
-
703
-
704
- def foreach(
705
- value: Callable,
706
- iterable: Iterable | AsyncIterable,
707
- /,
708
- *iterables: Iterable | AsyncIterable,
709
- threaded: bool = False,
710
- ):
711
- """
712
- """
713
- if (not threaded and
714
- isinstance(iterable, Iterable) and
715
- all(isinstance(it, Iterable) for it in iterables)
716
- ):
717
- if iterables:
718
- for args in _zip(iterable, *iterables):
719
- value(*args)
720
- else:
721
- for arg in iterable:
722
- value(arg)
723
- else:
724
- return async_foreach(value, iterable, *iterables, threaded=threaded)
725
-
726
-
727
- async def async_foreach(
728
- value: Callable,
729
- iterable: Iterable | AsyncIterable,
730
- /,
731
- *iterables: Iterable | AsyncIterable,
732
- threaded: bool = False,
733
- ):
734
- """
735
- """
736
- value = ensure_async(value, threaded=threaded)
737
- if iterables:
738
- async for args in async_zip(iterable, *iterables, threaded=threaded):
739
- await value(*args)
740
- else:
741
- async for arg in ensure_aiter(iterable, threaded=threaded):
742
- await value(arg)
743
-
744
-
745
- def through(
746
- iterable: Iterable | AsyncIterable,
747
- /,
748
- take_while: None | Callable = None,
749
- threaded: bool = False,
750
- ):
751
- """
752
- """
753
- if threaded or not isinstance(iterable, Iterable):
754
- return async_through(iterable, take_while, threaded=threaded)
755
- elif take_while is None:
756
- for _ in iterable:
757
- pass
758
- else:
759
- for v in _map(take_while, iterable):
760
- if not v:
761
- break
762
-
763
-
764
- async def async_through(
765
- iterable: Iterable | AsyncIterable,
766
- /,
767
- take_while: None | Callable = None,
768
- threaded: bool = False,
769
- ):
770
- """
771
- """
772
- iterable = ensure_aiter(iterable, threaded=threaded)
773
- if take_while is None:
774
- async for _ in iterable:
775
- pass
776
- elif take_while is bool:
777
- async for v in iterable:
778
- if not v:
779
- break
780
- else:
781
- async for v in async_map(take_while, iterable):
782
- if not v:
783
- break
784
-
785
-
786
- @overload
787
- def flatten(
788
- iterable: Iterable,
789
- /,
790
- exclude_types: type | tuple[type, ...] = (Buffer, str),
791
- *,
792
- threaded: Literal[False] = False,
793
- ) -> Iterator:
794
- ...
795
- @overload
796
- def flatten(
797
- iterable: Iterable,
798
- /,
799
- exclude_types: type | tuple[type, ...] = (Buffer, str),
800
- *,
801
- threaded: Literal[True],
802
- ) -> Iterator:
803
- ...
804
- @overload
805
- def flatten(
806
- iterable: AsyncIterable,
807
- /,
808
- exclude_types: type | tuple[type, ...] = (Buffer, str),
809
- *,
810
- threaded: Literal[False, True] = False,
811
- ) -> AsyncIterator:
812
- ...
813
- def flatten(
814
- iterable: Iterable | AsyncIterable,
815
- /,
816
- exclude_types: type | tuple[type, ...] = (Buffer, str),
817
- threaded: Literal[False, True] = False,
818
- ) -> Iterator | AsyncIterator:
819
- """
820
- """
821
- if threaded or not isinstance(iterable, Iterable):
822
- return async_flatten(iterable, exclude_types, threaded=threaded)
823
- def gen(iterable):
824
- for e in iterable:
825
- if isinstance(e, (Iterable, AsyncIterable)) and not isinstance(e, exclude_types):
826
- yield from gen(e)
827
- else:
828
- yield e
829
- return gen(iterable)
830
-
831
-
832
- async def async_flatten(
833
- iterable: Iterable | AsyncIterable,
834
- /,
835
- exclude_types: type | tuple[type, ...] = (Buffer, str),
836
- threaded: bool = False,
837
- ) -> AsyncIterator:
838
- """
839
- """
840
- async for e in ensure_aiter(iterable, threaded=threaded):
841
- if isinstance(e, (Iterable, AsyncIterable)) and not isinstance(e, exclude_types):
842
- async for e in async_flatten(e, exclude_types, threaded=threaded):
843
- yield e
844
- else:
845
- yield e
846
-
847
-
848
- @overload
849
- def collect[K, V](
850
- iterable: Iterable[tuple[K, V]] | Mapping[K, V],
851
- /,
852
- rettype: Callable[[Iterable[tuple[K, V]]], MutableMapping[K, V]],
853
- *,
854
- threaded: Literal[False] = False,
855
- ) -> MutableMapping[K, V]:
856
- ...
857
- @overload
858
- def collect[T](
859
- iterable: Iterable[T],
860
- /,
861
- rettype: Callable[[Iterable[T]], Collection[T]] = list,
862
- *,
863
- threaded: Literal[False] = False,
864
- ) -> Collection[T]:
865
- ...
866
- @overload
867
- def collect[K, V](
868
- iterable: Iterable[tuple[K, V]] | Mapping[K, V],
869
- /,
870
- rettype: Callable[[Iterable[tuple[K, V]]], MutableMapping[K, V]],
871
- *,
872
- threaded: Literal[True],
873
- ) -> Coroutine[Any, Any, MutableMapping[K, V]]:
874
- ...
875
- @overload
876
- def collect[T](
877
- iterable: Iterable[T],
878
- /,
879
- rettype: Callable[[Iterable[T]], Collection[T]] = list,
880
- *,
881
- threaded: Literal[True],
882
- ) -> Coroutine[Any, Any, Collection[T]]:
883
- ...
884
- @overload
885
- def collect[K, V](
886
- iterable: AsyncIterable[tuple[K, V]],
887
- /,
888
- rettype: Callable[[Iterable[tuple[K, V]]], MutableMapping[K, V]],
889
- *,
890
- threaded: Literal[False, True] = False,
891
- ) -> Coroutine[Any, Any, MutableMapping[K, V]]:
892
- ...
893
- @overload
894
- def collect[T](
895
- iterable: AsyncIterable[T],
896
- /,
897
- rettype: Callable[[Iterable[T]], Collection[T]] = list,
898
- *,
899
- threaded: Literal[False, True] = False,
900
- ) -> Coroutine[Any, Any, Collection[T]]:
901
- ...
902
- def collect(
903
- iterable: Iterable | AsyncIterable | Mapping,
904
- /,
905
- rettype: Callable[[Iterable], Collection] = list,
906
- *,
907
- threaded: Literal[False, True] = False,
908
- ) -> Collection | Coroutine[Any, Any, Collection]:
909
- """
910
- """
911
- if threaded or not isinstance(iterable, Iterable):
912
- return async_collect(iterable, rettype, threaded=threaded)
913
- return rettype(iterable)
914
-
915
-
916
- @overload
917
- def group_collect[K, V, C: Container](
918
- iterable: Iterable[tuple[K, V]],
919
- mapping: None = None,
920
- factory: None | C | Callable[[], C] = None,
921
- threaded: bool = False,
922
- ) -> dict[K, C]:
923
- ...
924
- @overload
925
- def group_collect[K, V, C: Container, M: MutableMapping](
926
- iterable: Iterable[tuple[K, V]],
927
- mapping: M,
928
- factory: None | C | Callable[[], C] = None,
929
- threaded: bool = False,
930
- ) -> M:
931
- ...
932
- @overload
933
- def group_collect[K, V, C: Container](
934
- iterable: AsyncIterable[tuple[K, V]],
935
- mapping: None = None,
936
- factory: None | C | Callable[[], C] = None,
937
- threaded: bool = False,
938
- ) -> Coroutine[Any, Any, dict[K, C]]:
939
- ...
940
- @overload
941
- def group_collect[K, V, C: Container, M: MutableMapping](
942
- iterable: AsyncIterable[tuple[K, V]],
943
- mapping: M,
944
- factory: None | C | Callable[[], C] = None,
945
- threaded: bool = False,
946
- ) -> Coroutine[Any, Any, M]:
947
- ...
948
- def group_collect[K, V, C: Container, M: MutableMapping](
949
- iterable: Iterable[tuple[K, V]] | AsyncIterable[tuple[K, V]],
950
- mapping: None | M = None,
951
- factory: None | C | Callable[[], C] = None,
952
- threaded: bool = False,
953
- ) -> dict[K, C] | M | Coroutine[Any, Any, dict[K, C]] | Coroutine[Any, Any, M]:
954
- """
955
- """
956
- if threaded or not isinstance(iterable, Iterable):
957
- return async_group_collect(iterable, mapping, factory, threaded=threaded)
958
- if factory is None:
959
- if isinstance(mapping, defaultdict):
960
- factory = mapping.default_factory
961
- elif mapping:
962
- factory = type(next(iter(ValuesView(mapping))))
963
- else:
964
- factory = cast(type[C], list)
965
- elif callable(factory):
966
- pass
967
- elif isinstance(factory, Container):
968
- factory = cast(Callable[[], C], lambda _obj=factory: copy(_obj))
969
- else:
970
- raise ValueError("can't determine factory")
971
- factory = cast(Callable[[], C], factory)
972
- if isinstance(factory, type):
973
- factory_type = factory
974
- else:
975
- factory_type = type(factory())
976
- if issubclass(factory_type, MutableSequence):
977
- add = getattr(factory_type, "append")
978
- else:
979
- add = getattr(factory_type, "add")
980
- if mapping is None:
981
- mapping = cast(M, {})
982
- for k, v in iterable:
983
- try:
984
- c = mapping[k]
985
- except LookupError:
986
- c = mapping[k] = factory()
987
- add(c, v)
988
- return mapping
989
-
990
-
991
- @overload
992
- async def async_group_collect[K, V, C: Container](
993
- iterable: Iterable[tuple[K, V]] | AsyncIterable[tuple[K, V]],
994
- mapping: None = None,
995
- factory: None | C | Callable[[], C] = None,
996
- threaded: bool = False,
997
- ) -> dict[K, C]:
998
- ...
999
- @overload
1000
- async def async_group_collect[K, V, C: Container, M: MutableMapping](
1001
- iterable: Iterable[tuple[K, V]] | AsyncIterable[tuple[K, V]],
1002
- mapping: M,
1003
- factory: None | C | Callable[[], C] = None,
1004
- threaded: bool = False,
1005
- ) -> M:
1006
- ...
1007
- async def async_group_collect[K, V, C: Container, M: MutableMapping](
1008
- iterable: Iterable[tuple[K, V]] | AsyncIterable[tuple[K, V]],
1009
- mapping: None | M = None,
1010
- factory: None | C | Callable[[], C] = None,
1011
- threaded: bool = False,
1012
- ) -> dict[K, C] | M:
1013
- """
1014
- """
1015
- iterable = ensure_aiter(iterable, threaded=threaded)
1016
- if factory is None:
1017
- if isinstance(mapping, defaultdict):
1018
- factory = mapping.default_factory
1019
- elif mapping:
1020
- factory = type(next(iter(ValuesView(mapping))))
1021
- else:
1022
- factory = cast(type[C], list)
1023
- elif callable(factory):
1024
- pass
1025
- elif isinstance(factory, Container):
1026
- factory = cast(Callable[[], C], lambda _obj=factory: copy(_obj))
1027
- else:
1028
- raise ValueError("can't determine factory")
1029
- factory = cast(Callable[[], C], factory)
1030
- if isinstance(factory, type):
1031
- factory_type = factory
1032
- else:
1033
- factory_type = type(factory())
1034
- if issubclass(factory_type, MutableSequence):
1035
- add = getattr(factory_type, "append")
1036
- else:
1037
- add = getattr(factory_type, "add")
1038
- if mapping is None:
1039
- mapping = cast(M, {})
1040
- async for k, v in iterable:
1041
- try:
1042
- c = mapping[k]
1043
- except LookupError:
1044
- c = mapping[k] = factory()
1045
- add(c, v)
1046
- return mapping
1047
-
1048
-
1049
- @overload
1050
- def iter_unique[T](
1051
- iterable: Iterable[T],
1052
- /,
1053
- seen: None | MutableSet = None,
1054
- *,
1055
- threaded: Literal[False] = False,
1056
- ) -> Iterator[T]:
1057
- ...
1058
- @overload
1059
- def iter_unique[T](
1060
- iterable: Iterable[T],
1061
- /,
1062
- seen: None | MutableSet = None,
1063
- *,
1064
- threaded: Literal[True],
1065
- ) -> AsyncIterator[T]:
1066
- ...
1067
- @overload
1068
- def iter_unique[T](
1069
- iterable: AsyncIterable[T],
1070
- /,
1071
- seen: None | MutableSet = None,
1072
- *,
1073
- threaded: Literal[False, True] = False,
1074
- ) -> AsyncIterator[T]:
1075
- ...
1076
- def iter_unique[T](
1077
- iterable: Iterable[T] | AsyncIterable[T],
1078
- /,
1079
- seen: None | MutableSet = None,
1080
- threaded: Literal[False, True] = False,
1081
- ) -> Iterator[T] | AsyncIterator[T]:
1082
- """
1083
- """
1084
- if threaded or not isinstance(iterable, Iterable):
1085
- return async_iter_unique(iterable, seen, threaded=threaded)
1086
- if seen is None:
1087
- seen = set()
1088
- def gen(iterable):
1089
- add = seen.add
1090
- for e in iterable:
1091
- if e not in seen:
1092
- yield e
1093
- add(e)
1094
- return gen(iterable)
1095
-
1096
-
1097
- async def async_iter_unique[T](
1098
- iterable: Iterable[T] | AsyncIterable[T],
1099
- /,
1100
- seen: None | MutableSet = None,
1101
- threaded: bool = False,
1102
- ) -> AsyncIterator[T]:
1103
- """
1104
- """
1105
- if seen is None:
1106
- seen = set()
1107
- add = seen.add
1108
- async for e in ensure_aiter(iterable, threaded=threaded):
1109
- if e not in seen:
1110
- yield e
1111
- add(e)
1112
-
1113
-
1114
- @overload
1115
- def wrap_iter[T](
1116
- iterable: Iterable[T],
1117
- /,
1118
- callprev: None | Callable[[T], Any] = None,
1119
- callnext: None | Callable[[T], Any] = None,
1120
- *,
1121
- threaded: Literal[False] = False,
1122
- ) -> Iterator[T]:
1123
- ...
1124
- @overload
1125
- def wrap_iter[T](
1126
- iterable: Iterable[T],
1127
- /,
1128
- callprev: None | Callable[[T], Any] = None,
1129
- callnext: None | Callable[[T], Any] = None,
1130
- *,
1131
- threaded: Literal[True],
1132
- ) -> AsyncIterator[T]:
1133
- ...
1134
- @overload
1135
- def wrap_iter[T](
1136
- iterable: AsyncIterable[T],
1137
- /,
1138
- callprev: None | Callable[[T], Any] = None,
1139
- callnext: None | Callable[[T], Any] = None,
1140
- threaded: Literal[False, True] = False,
1141
- ) -> AsyncIterator[T]:
1142
- ...
1143
- def wrap_iter[T](
1144
- iterable: Iterable[T] | AsyncIterable[T],
1145
- /,
1146
- callprev: None | Callable[[T], Any] = None,
1147
- callnext: None | Callable[[T], Any] = None,
1148
- threaded: bool = False,
1149
- ) -> Iterator[T] | AsyncIterator[T]:
1150
- """
1151
- """
1152
- if threaded or not isinstance(iterable, Iterable):
1153
- return wrap_aiter(
1154
- iterable,
1155
- callprev=callprev,
1156
- callnext=callnext,
1157
- threaded=threaded,
1158
- )
1159
- if not callable(callprev):
1160
- callprev = None
1161
- if not callable(callnext):
1162
- callnext = None
1163
- def gen():
1164
- for e in iterable:
1165
- callprev and callprev(e)
1166
- yield e
1167
- callnext and callnext(e)
1168
- return gen()
1169
-
1170
-
1171
- async def wrap_aiter[T](
1172
- iterable: Iterable[T] | AsyncIterable[T],
1173
- /,
1174
- callprev: None | Callable[[T], Any] = None,
1175
- callnext: None | Callable[[T], Any] = None,
1176
- threaded: bool = False,
1177
- ) -> AsyncIterator[T]:
1178
- """
1179
- """
1180
- callprev = ensure_async(callprev, threaded=threaded) if callable(callprev) else None
1181
- callnext = ensure_async(callnext, threaded=threaded) if callable(callnext) else None
1182
- async for e in ensure_aiter(iterable, threaded=threaded):
1183
- callprev and await callprev(e)
1184
- yield e
1185
- callnext and await callnext(e)
1186
-
1187
-
1188
- @overload
1189
- def peek_iter[T](
1190
- iterable: Iterable[T],
1191
- /,
1192
- threaded: Literal[False] = False,
1193
- ) -> None | Iterator[T]:
1194
- ...
1195
- @overload
1196
- def peek_iter[T](
1197
- iterable: Iterable[T],
1198
- /,
1199
- threaded: Literal[True],
1200
- ) -> Coroutine[Any, Any, None | AsyncIterator[T]]:
1201
- ...
1202
- @overload
1203
- def peek_iter[T](
1204
- iterable: AsyncIterable[T],
1205
- /,
1206
- threaded: Literal[False, True] = False,
1207
- ) -> Coroutine[Any, Any, None | AsyncIterator[T]]:
1208
- ...
1209
- def peek_iter[T](
1210
- iterable: Iterable[T] | AsyncIterable[T],
1211
- /,
1212
- threaded: Literal[False, True] = False,
1213
- ) -> None | Iterator[T] | Coroutine[Any, Any, None | AsyncIterator[T]]:
1214
- if threaded or isinstance(iterable, AsyncIterable):
1215
- return peek_aiter(iterable, threaded=threaded)
1216
- try:
1217
- it = iter(iterable)
1218
- first = next(it)
1219
- return chain((first,), it)
1220
- except StopIteration:
1221
- return None
1222
-
1223
-
1224
- async def peek_aiter[T](
1225
- iterable: Iterable[T] | AsyncIterable[T],
1226
- /,
1227
- threaded: Literal[False, True] = False,
1228
- ) -> None | AsyncIterator[T]:
1229
- try:
1230
- it = ensure_aiter(iterable, threaded=threaded)
1231
- first = await anext(it)
1232
- return async_chain((first,), it)
1233
- except StopAsyncIteration:
1234
- return None
1235
-
1236
-
1237
- def acc_step(
1238
- start: int,
1239
- stop: None | int = None,
1240
- step: int = 1,
1241
- ) -> Iterator[tuple[int, int, int]]:
1242
- """
1243
- """
1244
- if stop is None:
1245
- start, stop = 0, start
1246
- for i in range(start + step, stop, step):
1247
- yield start, (start := i), step
1248
- if start != stop:
1249
- yield start, stop, stop - start
1250
-
1251
-
1252
- def cut_iter(
1253
- start: int,
1254
- stop: None | int = None,
1255
- step: int = 1,
1256
- ) -> Iterator[tuple[int, int]]:
1257
- """
1258
- """
1259
- if stop is None:
1260
- start, stop = 0, start
1261
- for start in range(start + step, stop, step):
1262
- yield start, step
1263
- if start != stop:
1264
- yield stop, stop - start
1265
-
1266
-
1267
- @overload
1268
- def bfs_gen[T](
1269
- initial: T,
1270
- /,
1271
- unpack_iterator: Literal[False] = False,
1272
- ) -> Generator[T, T | None, None]:
1273
- ...
1274
- @overload
1275
- def bfs_gen[T](
1276
- initial: T | Iterator[T],
1277
- /,
1278
- unpack_iterator: Literal[True],
1279
- ) -> Generator[T, T | None, None]:
1280
- ...
1281
- def bfs_gen[T](
1282
- initial: T | Iterator[T],
1283
- /,
1284
- unpack_iterator: bool = False,
1285
- ) -> Generator[T, T | None, None]:
1286
- """辅助函数,返回生成器,用来简化广度优先遍历
1287
- """
1288
- dq: deque[T] = deque()
1289
- push, pushmany, pop = dq.append, dq.extend, dq.popleft
1290
- if isinstance(initial, Iterator) and unpack_iterator:
1291
- pushmany(initial)
1292
- else:
1293
- push(initial) # type: ignore
1294
- while dq:
1295
- args: None | T = yield (val := pop())
1296
- if unpack_iterator:
1297
- while args is not None:
1298
- if isinstance(args, Iterator):
1299
- pushmany(args)
1300
- else:
1301
- push(args)
1302
- args = yield val
1303
- else:
1304
- while args is not None:
1305
- push(args)
1306
- args = yield val
1307
-
1308
-
1309
- @overload
1310
- def context[T](
1311
- func: Callable[..., T],
1312
- *ctxs: ContextManager,
1313
- async_: Literal[False],
1314
- ) -> T:
1315
- ...
1316
- @overload
1317
- def context[T](
1318
- func: Callable[..., T] | Callable[..., Awaitable[T]],
1319
- *ctxs: ContextManager | AsyncContextManager,
1320
- async_: Literal[True],
1321
- ) -> Coroutine[Any, Any, T]:
1322
- ...
1323
- @overload
1324
- def context[T](
1325
- func: Callable[..., T] | Callable[..., Awaitable[T]],
1326
- *ctxs: ContextManager | AsyncContextManager,
1327
- async_: None = None,
1328
- ) -> T | Coroutine[Any, Any, T]:
1329
- ...
1330
- def context[T](
1331
- func: Callable[..., T] | Callable[..., Awaitable[T]],
1332
- *ctxs: ContextManager | AsyncContextManager,
1333
- async_: None | Literal[False, True] = None,
1334
- ) -> T | Coroutine[Any, Any, T]:
1335
- """
1336
- """
1337
- if async_ is None:
1338
- if iscoroutinefunction(func):
1339
- async_ = True
1340
- else:
1341
- async_ = _get_async()
1342
- if async_:
1343
- async def call():
1344
- args: list = []
1345
- add_arg = args.append
1346
- with ExitStack() as stack:
1347
- async with AsyncExitStack() as async_stack:
1348
- enter = stack.enter_context
1349
- async_enter = async_stack.enter_async_context
1350
- for ctx in ctxs:
1351
- if isinstance(ctx, AsyncContextManager):
1352
- add_arg(await async_enter(ctx))
1353
- else:
1354
- add_arg(enter(ctx))
1355
- ret = func(*args)
1356
- if isawaitable(ret):
1357
- ret = await cast(Awaitable, ret)
1358
- return ret
1359
- return call()
1360
- else:
1361
- with ExitStack() as stack:
1362
- return func(*map(stack.enter_context, ctxs)) # type: ignore
1363
-
1364
-
1365
- @overload
1366
- def backgroud_loop(
1367
- call: None | Callable = None,
1368
- /,
1369
- interval: int | float = 0.05,
1370
- *,
1371
- async_: Literal[False],
1372
- ) -> ContextManager:
1373
- ...
1374
- @overload
1375
- def backgroud_loop(
1376
- call: None | Callable = None,
1377
- /,
1378
- interval: int | float = 0.05,
1379
- *,
1380
- async_: Literal[True],
1381
- ) -> AsyncContextManager:
1382
- ...
1383
- @overload
1384
- def backgroud_loop(
1385
- call: None | Callable = None,
1386
- /,
1387
- interval: int | float = 0.05,
1388
- *,
1389
- async_: None = None,
1390
- ) -> ContextManager | AsyncContextManager:
1391
- ...
1392
- def backgroud_loop(
1393
- call: None | Callable = None,
1394
- /,
1395
- interval: int | float = 0.05,
1396
- *,
1397
- async_: None | Literal[False, True] = None,
1398
- ) -> ContextManager | AsyncContextManager:
1399
- """
1400
- """
1401
- if async_ is None:
1402
- if iscoroutinefunction(call):
1403
- async_ = True
1404
- else:
1405
- async_ = _get_async()
1406
- use_default_call = not callable(call)
1407
- if use_default_call:
1408
- start = time()
1409
- def call():
1410
- print(f"\r\x1b[K{format_time(time() - start)}", end="")
1411
- def run():
1412
- while running:
1413
- try:
1414
- yield call
1415
- except Exception:
1416
- pass
1417
- if interval > 0:
1418
- if async_:
1419
- yield async_sleep(interval)
1420
- else:
1421
- sleep(interval)
1422
- running = True
1423
- if async_:
1424
- @asynccontextmanager
1425
- async def actx():
1426
- nonlocal running
1427
- try:
1428
- task = create_task(run())
1429
- yield task
1430
- finally:
1431
- running = False
1432
- task.cancel()
1433
- if use_default_call:
1434
- print("\r\x1b[K", end="")
1435
- return actx()
1436
- else:
1437
- @contextmanager
1438
- def ctx():
1439
- nonlocal running
1440
- try:
1441
- yield start_new_thread(run, ())
1442
- finally:
1443
- running = False
1444
- if use_default_call:
1445
- print("\r\x1b[K", end="")
1446
- return ctx()
5
+ __version__ = (0, 3, 0)
1447
6
 
7
+ from .dynamic import *
8
+ from .gen_step import *
9
+ from .misc import *