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