python-iterutils 0.2.10.2__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/misc.py ADDED
@@ -0,0 +1,1062 @@
1
+ #!/usr/bin/env python3
2
+ # encoding: utf-8
3
+
4
+ __all__ = [
5
+ "iterable", "async_iterable", "map", "filter", "reduce", "zip",
6
+ "chain", "chain_from_iterable", "chunked", "foreach", "async_foreach",
7
+ "through", "async_through", "flatten", "async_flatten", "collect",
8
+ "async_collect", "group_collect", "async_group_collect", "iter_unique",
9
+ "async_iter_unique", "wrap_iter", "wrap_aiter", "peek_iter", "peek_aiter",
10
+ "acc_step", "cut_iter", "context", "backgroud_loop", "gen_startup",
11
+ "async_gen_startup", "do_iter", "do_aiter", "bfs_iter", "bfs_gen",
12
+ ]
13
+
14
+ from asyncio import create_task, sleep as async_sleep
15
+ from builtins import map as _map, filter as _filter, zip as _zip
16
+ from collections import defaultdict, deque
17
+ from collections.abc import (
18
+ AsyncGenerator, AsyncIterable, AsyncIterator, Awaitable, Buffer,
19
+ Callable, Collection, Container, Coroutine, Generator, Iterable,
20
+ Iterator, Mapping, MutableMapping, MutableSet, MutableSequence,
21
+ Sequence, ValuesView,
22
+ )
23
+ from contextlib import asynccontextmanager, contextmanager, ExitStack, AsyncExitStack
24
+ from copy import copy
25
+ from functools import update_wrapper
26
+ from itertools import batched, chain as _chain, pairwise
27
+ from inspect import iscoroutinefunction
28
+ from sys import _getframe
29
+ from _thread import start_new_thread
30
+ from time import sleep, time
31
+ from types import FrameType
32
+ from typing import (
33
+ cast, overload, Any, AsyncContextManager, ContextManager, Literal,
34
+ )
35
+
36
+ from asynctools import (
37
+ async_filter, async_map, async_reduce, async_zip, async_batched,
38
+ ensure_async, ensure_aiter, async_chain, collect as async_collect,
39
+ )
40
+ from texttools import format_time
41
+ from undefined import undefined
42
+
43
+
44
+ iterable = Iterable.__instancecheck__
45
+ async_iterable = AsyncIterable.__instancecheck__
46
+ isawaitable = Awaitable.__instancecheck__
47
+
48
+
49
+ def _coalesce(vals, default=None):
50
+ for val in vals:
51
+ if val is not None:
52
+ return val
53
+ return default
54
+
55
+
56
+ @overload
57
+ def _get_async(back: int = 2, /, *, default: Literal[False] = False) -> bool:
58
+ ...
59
+ @overload
60
+ def _get_async[T](back: int = 2, /, *, default: T) -> bool | T:
61
+ ...
62
+ def _get_async[T](back: int = 2, /, *, default: Literal[False] | T = False) -> bool | T:
63
+ """往上查找,从最近的调用栈的命名空间中获取 `async_` 的值
64
+ """
65
+ def iter_frams(f: None | FrameType = _getframe(back)):
66
+ while f:
67
+ yield f.f_locals.get("async_")
68
+ f = f.f_back
69
+ return _coalesce(iter_frams(), default)
70
+
71
+
72
+ def map(
73
+ function: None | Callable,
74
+ iterable: Iterable | AsyncIterable,
75
+ /,
76
+ *iterables: Iterable | AsyncIterable,
77
+ threaded: bool = False,
78
+ ):
79
+ """
80
+ """
81
+ if (
82
+ threaded or
83
+ iscoroutinefunction(function) or
84
+ isinstance(iterable, AsyncIterable) or
85
+ any(isinstance(i, AsyncIterable) for i in iterables)
86
+ ):
87
+ if function is None:
88
+ if iterables:
89
+ return async_zip(iterable, *iterables, threaded=threaded)
90
+ elif threaded:
91
+ return ensure_aiter(iterable, threaded=threaded)
92
+ else:
93
+ return iterable
94
+ return async_map(function, iterable, *iterables)
95
+ if function is None:
96
+ if iterables:
97
+ return _zip(iterable, *iterables)
98
+ else:
99
+ return iterable
100
+ return _map(function, iterable, *iterables)
101
+
102
+
103
+ def filter(
104
+ function: None | Callable,
105
+ iterable: Iterable | AsyncIterable,
106
+ /,
107
+ threaded: bool = False,
108
+ ):
109
+ """
110
+ """
111
+ if threaded or iscoroutinefunction(function) or isinstance(iterable, AsyncIterable):
112
+ return async_filter(function, iterable, threaded=threaded)
113
+ return _filter(function, iterable)
114
+
115
+
116
+ def reduce(
117
+ function: Callable,
118
+ iterable: Iterable | AsyncIterable,
119
+ initial: Any = undefined,
120
+ /,
121
+ threaded: bool = False,
122
+ ):
123
+ """
124
+ """
125
+ if threaded or iscoroutinefunction(function) or isinstance(iterable, AsyncIterable):
126
+ return async_reduce(function, iterable, initial, threaded=threaded)
127
+ from functools import reduce
128
+ if initial is undefined:
129
+ return reduce(function, iterable)
130
+ return reduce(function, iterable, initial)
131
+
132
+
133
+ def zip(
134
+ iterable: Iterable | AsyncIterable,
135
+ /,
136
+ *iterables: Iterable | AsyncIterable,
137
+ threaded: bool = False,
138
+ ):
139
+ """
140
+ """
141
+ if (not threaded and
142
+ isinstance(iterable, Iterable) and
143
+ all(isinstance(it, Iterable) for it in iterables)
144
+ ):
145
+ return _zip(iterable, *iterables)
146
+ return async_zip(iterable, *iterables, threaded=threaded)
147
+
148
+
149
+ @overload
150
+ def chain[T](
151
+ iterable: Iterable[T],
152
+ /,
153
+ *iterables: Iterable[T],
154
+ threaded: Literal[False] = False,
155
+ ) -> Iterator[T]:
156
+ ...
157
+ @overload
158
+ def chain[T](
159
+ iterable: Iterable[T],
160
+ /,
161
+ *iterables: Iterable[T] | AsyncIterable[T],
162
+ threaded: Literal[True],
163
+ ) -> AsyncIterator[T]:
164
+ ...
165
+ @overload
166
+ def chain[T](
167
+ iterable: AsyncIterable[T],
168
+ /,
169
+ *iterables: Iterable[T] | AsyncIterable[T],
170
+ threaded: Literal[False, True] = False,
171
+ ) -> AsyncIterator[T]:
172
+ ...
173
+ def chain[T](
174
+ iterable: Iterable[T] | AsyncIterable[T],
175
+ /,
176
+ *iterables: Iterable[T] | AsyncIterable[T],
177
+ threaded: Literal[False, True] = False,
178
+ ) -> Iterator[T] | AsyncIterator[T]:
179
+ if (not threaded and
180
+ isinstance(iterable, Iterable) and
181
+ all(isinstance(it, Iterable) for it in iterables)
182
+ ):
183
+ return _chain(iterable, *iterables) # type: ignore
184
+ return async_chain(iterable, *iterables, threaded=threaded)
185
+
186
+
187
+ @overload
188
+ def chain_from_iterable[T](
189
+ iterables: Iterable[Iterable[T]],
190
+ threaded: bool = False,
191
+ *,
192
+ async_: Literal[False] = False,
193
+ ) -> Iterator[T]:
194
+ ...
195
+ @overload
196
+ def chain_from_iterable[T](
197
+ iterables: (
198
+ AsyncIterable[Iterable[T]] |
199
+ AsyncIterable[AsyncIterable[T]] |
200
+ AsyncIterable[Iterable[T] | AsyncIterable[T]]
201
+ ),
202
+ threaded: bool = False,
203
+ *,
204
+ async_: bool = False,
205
+ ) -> AsyncIterator[T]:
206
+ ...
207
+ @overload
208
+ def chain_from_iterable[T](
209
+ iterables: (
210
+ Iterable[Iterable[T]] |
211
+ Iterable[AsyncIterable[T]] |
212
+ Iterable[Iterable[T] | AsyncIterable[T]] |
213
+ AsyncIterable[Iterable[T]] |
214
+ AsyncIterable[AsyncIterable[T]] |
215
+ AsyncIterable[Iterable[T] | AsyncIterable[T]]
216
+ ),
217
+ threaded: bool = False,
218
+ *,
219
+ async_: Literal[True],
220
+ ) -> AsyncIterator[T]:
221
+ ...
222
+ def chain_from_iterable[T](
223
+ iterables: (
224
+ Iterable[Iterable[T]] |
225
+ Iterable[AsyncIterable[T]] |
226
+ Iterable[Iterable[T] | AsyncIterable[T]] |
227
+ AsyncIterable[Iterable[T]] |
228
+ AsyncIterable[AsyncIterable[T]] |
229
+ AsyncIterable[Iterable[T] | AsyncIterable[T]]
230
+ ),
231
+ threaded: bool = False,
232
+ *,
233
+ async_: Literal[False, True] = False,
234
+ ) -> Iterator[T] | AsyncIterator[T]:
235
+ if async_ or not isinstance(iterables, Iterable):
236
+ if isinstance(iterables, Iterable):
237
+ return async_chain.from_iterable(iterables, threaded=threaded)
238
+ else:
239
+ return async_chain.from_iterable(iterables, threaded=threaded)
240
+ return _chain.from_iterable(iterables) # type: ignore
241
+
242
+ setattr(chain, "from_iterable", chain_from_iterable)
243
+
244
+
245
+ @overload
246
+ def chunked[T](
247
+ iterable: Iterable[T],
248
+ n: int = 1,
249
+ /,
250
+ *,
251
+ threaded: Literal[False] = False,
252
+ ) -> Iterator[Sequence[T]]:
253
+ ...
254
+ @overload
255
+ def chunked[T](
256
+ iterable: Iterable[T],
257
+ n: int = 1,
258
+ /,
259
+ *,
260
+ threaded: Literal[True],
261
+ ) -> Iterator[Sequence[T]]:
262
+ ...
263
+ @overload
264
+ def chunked[T](
265
+ iterable: AsyncIterable[T],
266
+ n: int = 1,
267
+ /,
268
+ *,
269
+ threaded: Literal[False, True] = False,
270
+ ) -> AsyncIterator[Sequence[T]]:
271
+ ...
272
+ def chunked[T](
273
+ iterable: Iterable[T] | AsyncIterable[T],
274
+ n: int = 1,
275
+ /,
276
+ *,
277
+ threaded: Literal[False, True] = False,
278
+ ) -> Iterator[Sequence[T]] | AsyncIterator[Sequence[T]]:
279
+ """
280
+ """
281
+ if n < 0:
282
+ n = 1
283
+ if isinstance(iterable, Sequence):
284
+ if n == 1:
285
+ return ((e,) for e in iterable)
286
+ return (iterable[i:j] for i, j in pairwise(range(0, len(iterable)+n, n)))
287
+ elif not threaded and isinstance(iterable, Iterable):
288
+ return batched(iterable, n)
289
+ else:
290
+ return async_batched(iterable, n, threaded=threaded)
291
+
292
+
293
+ def foreach(
294
+ value: Callable,
295
+ iterable: Iterable | AsyncIterable,
296
+ /,
297
+ *iterables: Iterable | AsyncIterable,
298
+ threaded: bool = False,
299
+ ):
300
+ """
301
+ """
302
+ if (not threaded and
303
+ isinstance(iterable, Iterable) and
304
+ all(isinstance(it, Iterable) for it in iterables)
305
+ ):
306
+ if iterables:
307
+ for args in _zip(iterable, *iterables):
308
+ value(*args)
309
+ else:
310
+ for arg in iterable:
311
+ value(arg)
312
+ else:
313
+ return async_foreach(value, iterable, *iterables, threaded=threaded)
314
+
315
+
316
+ async def async_foreach(
317
+ value: Callable,
318
+ iterable: Iterable | AsyncIterable,
319
+ /,
320
+ *iterables: Iterable | AsyncIterable,
321
+ threaded: bool = False,
322
+ ):
323
+ """
324
+ """
325
+ value = ensure_async(value, threaded=threaded)
326
+ if iterables:
327
+ async for args in async_zip(iterable, *iterables, threaded=threaded):
328
+ await value(*args)
329
+ else:
330
+ async for arg in ensure_aiter(iterable, threaded=threaded):
331
+ await value(arg)
332
+
333
+
334
+ def through(
335
+ iterable: Iterable | AsyncIterable,
336
+ /,
337
+ take_while: None | Callable = None,
338
+ threaded: bool = False,
339
+ ):
340
+ """
341
+ """
342
+ if threaded or not isinstance(iterable, Iterable):
343
+ return async_through(iterable, take_while, threaded=threaded)
344
+ elif take_while is None:
345
+ for _ in iterable:
346
+ pass
347
+ else:
348
+ for v in _map(take_while, iterable):
349
+ if not v:
350
+ break
351
+
352
+
353
+ async def async_through(
354
+ iterable: Iterable | AsyncIterable,
355
+ /,
356
+ take_while: None | Callable = None,
357
+ threaded: bool = False,
358
+ ):
359
+ """
360
+ """
361
+ iterable = ensure_aiter(iterable, threaded=threaded)
362
+ if take_while is None:
363
+ async for _ in iterable:
364
+ pass
365
+ elif take_while is bool:
366
+ async for v in iterable:
367
+ if not v:
368
+ break
369
+ else:
370
+ async for v in async_map(take_while, iterable):
371
+ if not v:
372
+ break
373
+
374
+
375
+ @overload
376
+ def flatten(
377
+ iterable: Iterable,
378
+ /,
379
+ exclude_types: type | tuple[type, ...] = (Buffer, str),
380
+ *,
381
+ threaded: Literal[False] = False,
382
+ ) -> Iterator:
383
+ ...
384
+ @overload
385
+ def flatten(
386
+ iterable: Iterable,
387
+ /,
388
+ exclude_types: type | tuple[type, ...] = (Buffer, str),
389
+ *,
390
+ threaded: Literal[True],
391
+ ) -> Iterator:
392
+ ...
393
+ @overload
394
+ def flatten(
395
+ iterable: AsyncIterable,
396
+ /,
397
+ exclude_types: type | tuple[type, ...] = (Buffer, str),
398
+ *,
399
+ threaded: Literal[False, True] = False,
400
+ ) -> AsyncIterator:
401
+ ...
402
+ def flatten(
403
+ iterable: Iterable | AsyncIterable,
404
+ /,
405
+ exclude_types: type | tuple[type, ...] = (Buffer, str),
406
+ threaded: Literal[False, True] = False,
407
+ ) -> Iterator | AsyncIterator:
408
+ """
409
+ """
410
+ if threaded or not isinstance(iterable, Iterable):
411
+ return async_flatten(iterable, exclude_types, threaded=threaded)
412
+ def gen(iterable):
413
+ for e in iterable:
414
+ if isinstance(e, (Iterable, AsyncIterable)) and not isinstance(e, exclude_types):
415
+ yield from gen(e)
416
+ else:
417
+ yield e
418
+ return gen(iterable)
419
+
420
+
421
+ async def async_flatten(
422
+ iterable: Iterable | AsyncIterable,
423
+ /,
424
+ exclude_types: type | tuple[type, ...] = (Buffer, str),
425
+ threaded: bool = False,
426
+ ) -> AsyncIterator:
427
+ """
428
+ """
429
+ async for e in ensure_aiter(iterable, threaded=threaded):
430
+ if isinstance(e, (Iterable, AsyncIterable)) and not isinstance(e, exclude_types):
431
+ async for e in async_flatten(e, exclude_types, threaded=threaded):
432
+ yield e
433
+ else:
434
+ yield e
435
+
436
+
437
+ @overload
438
+ def collect[K, V](
439
+ iterable: Iterable[tuple[K, V]] | Mapping[K, V],
440
+ /,
441
+ rettype: Callable[[Iterable[tuple[K, V]]], MutableMapping[K, V]],
442
+ *,
443
+ threaded: Literal[False] = False,
444
+ ) -> MutableMapping[K, V]:
445
+ ...
446
+ @overload
447
+ def collect[T](
448
+ iterable: Iterable[T],
449
+ /,
450
+ rettype: Callable[[Iterable[T]], Collection[T]] = list,
451
+ *,
452
+ threaded: Literal[False] = False,
453
+ ) -> Collection[T]:
454
+ ...
455
+ @overload
456
+ def collect[K, V](
457
+ iterable: Iterable[tuple[K, V]] | Mapping[K, V],
458
+ /,
459
+ rettype: Callable[[Iterable[tuple[K, V]]], MutableMapping[K, V]],
460
+ *,
461
+ threaded: Literal[True],
462
+ ) -> Coroutine[Any, Any, MutableMapping[K, V]]:
463
+ ...
464
+ @overload
465
+ def collect[T](
466
+ iterable: Iterable[T],
467
+ /,
468
+ rettype: Callable[[Iterable[T]], Collection[T]] = list,
469
+ *,
470
+ threaded: Literal[True],
471
+ ) -> Coroutine[Any, Any, Collection[T]]:
472
+ ...
473
+ @overload
474
+ def collect[K, V](
475
+ iterable: AsyncIterable[tuple[K, V]],
476
+ /,
477
+ rettype: Callable[[Iterable[tuple[K, V]]], MutableMapping[K, V]],
478
+ *,
479
+ threaded: Literal[False, True] = False,
480
+ ) -> Coroutine[Any, Any, MutableMapping[K, V]]:
481
+ ...
482
+ @overload
483
+ def collect[T](
484
+ iterable: AsyncIterable[T],
485
+ /,
486
+ rettype: Callable[[Iterable[T]], Collection[T]] = list,
487
+ *,
488
+ threaded: Literal[False, True] = False,
489
+ ) -> Coroutine[Any, Any, Collection[T]]:
490
+ ...
491
+ def collect(
492
+ iterable: Iterable | AsyncIterable | Mapping,
493
+ /,
494
+ rettype: Callable[[Iterable], Collection] = list,
495
+ *,
496
+ threaded: Literal[False, True] = False,
497
+ ) -> Collection | Coroutine[Any, Any, Collection]:
498
+ """
499
+ """
500
+ if threaded or not isinstance(iterable, Iterable):
501
+ return async_collect(iterable, rettype, threaded=threaded)
502
+ return rettype(iterable)
503
+
504
+
505
+ @overload
506
+ def group_collect[K, V, C: Container](
507
+ iterable: Iterable[tuple[K, V]],
508
+ mapping: None = None,
509
+ factory: None | C | Callable[[], C] = None,
510
+ threaded: bool = False,
511
+ ) -> dict[K, C]:
512
+ ...
513
+ @overload
514
+ def group_collect[K, V, C: Container, M: MutableMapping](
515
+ iterable: Iterable[tuple[K, V]],
516
+ mapping: M,
517
+ factory: None | C | Callable[[], C] = None,
518
+ threaded: bool = False,
519
+ ) -> M:
520
+ ...
521
+ @overload
522
+ def group_collect[K, V, C: Container](
523
+ iterable: AsyncIterable[tuple[K, V]],
524
+ mapping: None = None,
525
+ factory: None | C | Callable[[], C] = None,
526
+ threaded: bool = False,
527
+ ) -> Coroutine[Any, Any, dict[K, C]]:
528
+ ...
529
+ @overload
530
+ def group_collect[K, V, C: Container, M: MutableMapping](
531
+ iterable: AsyncIterable[tuple[K, V]],
532
+ mapping: M,
533
+ factory: None | C | Callable[[], C] = None,
534
+ threaded: bool = False,
535
+ ) -> Coroutine[Any, Any, M]:
536
+ ...
537
+ def group_collect[K, V, C: Container, M: MutableMapping](
538
+ iterable: Iterable[tuple[K, V]] | AsyncIterable[tuple[K, V]],
539
+ mapping: None | M = None,
540
+ factory: None | C | Callable[[], C] = None,
541
+ threaded: bool = False,
542
+ ) -> dict[K, C] | M | Coroutine[Any, Any, dict[K, C]] | Coroutine[Any, Any, M]:
543
+ """
544
+ """
545
+ if threaded or not isinstance(iterable, Iterable):
546
+ return async_group_collect(iterable, mapping, factory, threaded=threaded)
547
+ if factory is None:
548
+ if isinstance(mapping, defaultdict):
549
+ factory = mapping.default_factory
550
+ elif mapping:
551
+ factory = type(next(iter(ValuesView(mapping))))
552
+ else:
553
+ factory = cast(type[C], list)
554
+ elif callable(factory):
555
+ pass
556
+ elif isinstance(factory, Container):
557
+ factory = cast(Callable[[], C], lambda _obj=factory: copy(_obj))
558
+ else:
559
+ raise ValueError("can't determine factory")
560
+ factory = cast(Callable[[], C], factory)
561
+ if isinstance(factory, type):
562
+ factory_type = factory
563
+ else:
564
+ factory_type = type(factory())
565
+ if issubclass(factory_type, MutableSequence):
566
+ add = getattr(factory_type, "append")
567
+ else:
568
+ add = getattr(factory_type, "add")
569
+ if mapping is None:
570
+ mapping = cast(M, {})
571
+ for k, v in iterable:
572
+ try:
573
+ c = mapping[k]
574
+ except LookupError:
575
+ c = mapping[k] = factory()
576
+ add(c, v)
577
+ return mapping
578
+
579
+
580
+ @overload
581
+ async def async_group_collect[K, V, C: Container](
582
+ iterable: Iterable[tuple[K, V]] | AsyncIterable[tuple[K, V]],
583
+ mapping: None = None,
584
+ factory: None | C | Callable[[], C] = None,
585
+ threaded: bool = False,
586
+ ) -> dict[K, C]:
587
+ ...
588
+ @overload
589
+ async def async_group_collect[K, V, C: Container, M: MutableMapping](
590
+ iterable: Iterable[tuple[K, V]] | AsyncIterable[tuple[K, V]],
591
+ mapping: M,
592
+ factory: None | C | Callable[[], C] = None,
593
+ threaded: bool = False,
594
+ ) -> M:
595
+ ...
596
+ async def async_group_collect[K, V, C: Container, M: MutableMapping](
597
+ iterable: Iterable[tuple[K, V]] | AsyncIterable[tuple[K, V]],
598
+ mapping: None | M = None,
599
+ factory: None | C | Callable[[], C] = None,
600
+ threaded: bool = False,
601
+ ) -> dict[K, C] | M:
602
+ """
603
+ """
604
+ iterable = ensure_aiter(iterable, threaded=threaded)
605
+ if factory is None:
606
+ if isinstance(mapping, defaultdict):
607
+ factory = mapping.default_factory
608
+ elif mapping:
609
+ factory = type(next(iter(ValuesView(mapping))))
610
+ else:
611
+ factory = cast(type[C], list)
612
+ elif callable(factory):
613
+ pass
614
+ elif isinstance(factory, Container):
615
+ factory = cast(Callable[[], C], lambda _obj=factory: copy(_obj))
616
+ else:
617
+ raise ValueError("can't determine factory")
618
+ factory = cast(Callable[[], C], factory)
619
+ if isinstance(factory, type):
620
+ factory_type = factory
621
+ else:
622
+ factory_type = type(factory())
623
+ if issubclass(factory_type, MutableSequence):
624
+ add = getattr(factory_type, "append")
625
+ else:
626
+ add = getattr(factory_type, "add")
627
+ if mapping is None:
628
+ mapping = cast(M, {})
629
+ async for k, v in iterable:
630
+ try:
631
+ c = mapping[k]
632
+ except LookupError:
633
+ c = mapping[k] = factory()
634
+ add(c, v)
635
+ return mapping
636
+
637
+
638
+ @overload
639
+ def iter_unique[T](
640
+ iterable: Iterable[T],
641
+ /,
642
+ seen: None | MutableSet = None,
643
+ *,
644
+ threaded: Literal[False] = False,
645
+ ) -> Iterator[T]:
646
+ ...
647
+ @overload
648
+ def iter_unique[T](
649
+ iterable: Iterable[T],
650
+ /,
651
+ seen: None | MutableSet = None,
652
+ *,
653
+ threaded: Literal[True],
654
+ ) -> AsyncIterator[T]:
655
+ ...
656
+ @overload
657
+ def iter_unique[T](
658
+ iterable: AsyncIterable[T],
659
+ /,
660
+ seen: None | MutableSet = None,
661
+ *,
662
+ threaded: Literal[False, True] = False,
663
+ ) -> AsyncIterator[T]:
664
+ ...
665
+ def iter_unique[T](
666
+ iterable: Iterable[T] | AsyncIterable[T],
667
+ /,
668
+ seen: None | MutableSet = None,
669
+ threaded: Literal[False, True] = False,
670
+ ) -> Iterator[T] | AsyncIterator[T]:
671
+ """
672
+ """
673
+ if threaded or not isinstance(iterable, Iterable):
674
+ return async_iter_unique(iterable, seen, threaded=threaded)
675
+ if seen is None:
676
+ seen = set()
677
+ def gen(iterable):
678
+ add = seen.add
679
+ for e in iterable:
680
+ if e not in seen:
681
+ yield e
682
+ add(e)
683
+ return gen(iterable)
684
+
685
+
686
+ async def async_iter_unique[T](
687
+ iterable: Iterable[T] | AsyncIterable[T],
688
+ /,
689
+ seen: None | MutableSet = None,
690
+ threaded: bool = False,
691
+ ) -> AsyncIterator[T]:
692
+ """
693
+ """
694
+ if seen is None:
695
+ seen = set()
696
+ add = seen.add
697
+ async for e in ensure_aiter(iterable, threaded=threaded):
698
+ if e not in seen:
699
+ yield e
700
+ add(e)
701
+
702
+
703
+ @overload
704
+ def wrap_iter[T](
705
+ iterable: Iterable[T],
706
+ /,
707
+ callprev: None | Callable[[T], Any] = None,
708
+ callnext: None | Callable[[T], Any] = None,
709
+ *,
710
+ threaded: Literal[False] = False,
711
+ ) -> Iterator[T]:
712
+ ...
713
+ @overload
714
+ def wrap_iter[T](
715
+ iterable: Iterable[T],
716
+ /,
717
+ callprev: None | Callable[[T], Any] = None,
718
+ callnext: None | Callable[[T], Any] = None,
719
+ *,
720
+ threaded: Literal[True],
721
+ ) -> AsyncIterator[T]:
722
+ ...
723
+ @overload
724
+ def wrap_iter[T](
725
+ iterable: AsyncIterable[T],
726
+ /,
727
+ callprev: None | Callable[[T], Any] = None,
728
+ callnext: None | Callable[[T], Any] = None,
729
+ threaded: Literal[False, True] = False,
730
+ ) -> AsyncIterator[T]:
731
+ ...
732
+ def wrap_iter[T](
733
+ iterable: Iterable[T] | AsyncIterable[T],
734
+ /,
735
+ callprev: None | Callable[[T], Any] = None,
736
+ callnext: None | Callable[[T], Any] = None,
737
+ threaded: bool = False,
738
+ ) -> Iterator[T] | AsyncIterator[T]:
739
+ """
740
+ """
741
+ if threaded or not isinstance(iterable, Iterable):
742
+ return wrap_aiter(
743
+ iterable,
744
+ callprev=callprev,
745
+ callnext=callnext,
746
+ threaded=threaded,
747
+ )
748
+ if not callable(callprev):
749
+ callprev = None
750
+ if not callable(callnext):
751
+ callnext = None
752
+ def gen():
753
+ for e in iterable:
754
+ callprev and callprev(e)
755
+ yield e
756
+ callnext and callnext(e)
757
+ return gen()
758
+
759
+
760
+ async def wrap_aiter[T](
761
+ iterable: Iterable[T] | AsyncIterable[T],
762
+ /,
763
+ callprev: None | Callable[[T], Any] = None,
764
+ callnext: None | Callable[[T], Any] = None,
765
+ threaded: bool = False,
766
+ ) -> AsyncIterator[T]:
767
+ """
768
+ """
769
+ callprev = ensure_async(callprev, threaded=threaded) if callable(callprev) else None
770
+ callnext = ensure_async(callnext, threaded=threaded) if callable(callnext) else None
771
+ async for e in ensure_aiter(iterable, threaded=threaded):
772
+ callprev and await callprev(e)
773
+ yield e
774
+ callnext and await callnext(e)
775
+
776
+
777
+ @overload
778
+ def peek_iter[T](
779
+ iterable: Iterable[T],
780
+ /,
781
+ threaded: Literal[False] = False,
782
+ ) -> None | Iterator[T]:
783
+ ...
784
+ @overload
785
+ def peek_iter[T](
786
+ iterable: Iterable[T],
787
+ /,
788
+ threaded: Literal[True],
789
+ ) -> Coroutine[Any, Any, None | AsyncIterator[T]]:
790
+ ...
791
+ @overload
792
+ def peek_iter[T](
793
+ iterable: AsyncIterable[T],
794
+ /,
795
+ threaded: Literal[False, True] = False,
796
+ ) -> Coroutine[Any, Any, None | AsyncIterator[T]]:
797
+ ...
798
+ def peek_iter[T](
799
+ iterable: Iterable[T] | AsyncIterable[T],
800
+ /,
801
+ threaded: Literal[False, True] = False,
802
+ ) -> None | Iterator[T] | Coroutine[Any, Any, None | AsyncIterator[T]]:
803
+ if threaded or isinstance(iterable, AsyncIterable):
804
+ return peek_aiter(iterable, threaded=threaded)
805
+ try:
806
+ it = iter(iterable)
807
+ first = next(it)
808
+ return chain((first,), it)
809
+ except StopIteration:
810
+ return None
811
+
812
+
813
+ async def peek_aiter[T](
814
+ iterable: Iterable[T] | AsyncIterable[T],
815
+ /,
816
+ threaded: Literal[False, True] = False,
817
+ ) -> None | AsyncIterator[T]:
818
+ try:
819
+ it = ensure_aiter(iterable, threaded=threaded)
820
+ first = await anext(it)
821
+ return async_chain((first,), it)
822
+ except StopAsyncIteration:
823
+ return None
824
+
825
+
826
+ def acc_step(
827
+ start: int,
828
+ stop: None | int = None,
829
+ step: int = 1,
830
+ ) -> Iterator[tuple[int, int, int]]:
831
+ """
832
+ """
833
+ if stop is None:
834
+ start, stop = 0, start
835
+ for i in range(start + step, stop, step):
836
+ yield start, (start := i), step
837
+ if start != stop:
838
+ yield start, stop, stop - start
839
+
840
+
841
+ def cut_iter(
842
+ start: int,
843
+ stop: None | int = None,
844
+ step: int = 1,
845
+ ) -> Iterator[tuple[int, int]]:
846
+ """
847
+ """
848
+ if stop is None:
849
+ start, stop = 0, start
850
+ for start in range(start + step, stop, step):
851
+ yield start, step
852
+ if start != stop:
853
+ yield stop, stop - start
854
+
855
+
856
+ @overload
857
+ def context[T](
858
+ func: Callable[..., T],
859
+ *ctxs: ContextManager,
860
+ async_: Literal[False],
861
+ ) -> T:
862
+ ...
863
+ @overload
864
+ def context[T](
865
+ func: Callable[..., T] | Callable[..., Awaitable[T]],
866
+ *ctxs: ContextManager | AsyncContextManager,
867
+ async_: Literal[True],
868
+ ) -> Coroutine[Any, Any, T]:
869
+ ...
870
+ @overload
871
+ def context[T](
872
+ func: Callable[..., T] | Callable[..., Awaitable[T]],
873
+ *ctxs: ContextManager | AsyncContextManager,
874
+ async_: None = None,
875
+ ) -> T | Coroutine[Any, Any, T]:
876
+ ...
877
+ def context[T](
878
+ func: Callable[..., T] | Callable[..., Awaitable[T]],
879
+ *ctxs: ContextManager | AsyncContextManager,
880
+ async_: None | Literal[False, True] = None,
881
+ ) -> T | Coroutine[Any, Any, T]:
882
+ """
883
+ """
884
+ if async_ is None:
885
+ if iscoroutinefunction(func):
886
+ async_ = True
887
+ else:
888
+ async_ = _get_async()
889
+ if async_:
890
+ async def call():
891
+ args: list = []
892
+ add_arg = args.append
893
+ with ExitStack() as stack:
894
+ async with AsyncExitStack() as async_stack:
895
+ enter = stack.enter_context
896
+ async_enter = async_stack.enter_async_context
897
+ for ctx in ctxs:
898
+ if isinstance(ctx, AsyncContextManager):
899
+ add_arg(await async_enter(ctx))
900
+ else:
901
+ add_arg(enter(ctx))
902
+ ret = func(*args)
903
+ if isawaitable(ret):
904
+ ret = await cast(Awaitable, ret)
905
+ return ret
906
+ return call()
907
+ else:
908
+ with ExitStack() as stack:
909
+ return func(*map(stack.enter_context, ctxs)) # type: ignore
910
+
911
+
912
+ @overload
913
+ def backgroud_loop(
914
+ call: None | Callable = None,
915
+ /,
916
+ interval: int | float = 0.05,
917
+ *,
918
+ async_: Literal[False],
919
+ ) -> ContextManager:
920
+ ...
921
+ @overload
922
+ def backgroud_loop(
923
+ call: None | Callable = None,
924
+ /,
925
+ interval: int | float = 0.05,
926
+ *,
927
+ async_: Literal[True],
928
+ ) -> AsyncContextManager:
929
+ ...
930
+ @overload
931
+ def backgroud_loop(
932
+ call: None | Callable = None,
933
+ /,
934
+ interval: int | float = 0.05,
935
+ *,
936
+ async_: None = None,
937
+ ) -> ContextManager | AsyncContextManager:
938
+ ...
939
+ def backgroud_loop(
940
+ call: None | Callable = None,
941
+ /,
942
+ interval: int | float = 0.05,
943
+ *,
944
+ async_: None | Literal[False, True] = None,
945
+ ) -> ContextManager | AsyncContextManager:
946
+ """
947
+ """
948
+ if async_ is None:
949
+ if iscoroutinefunction(call):
950
+ async_ = True
951
+ else:
952
+ async_ = _get_async()
953
+ use_default_call = not callable(call)
954
+ if use_default_call:
955
+ start = time()
956
+ def call():
957
+ print(f"\r\x1b[K{format_time(time() - start)}", end="")
958
+ def run():
959
+ while running:
960
+ try:
961
+ yield call
962
+ except Exception:
963
+ pass
964
+ if interval > 0:
965
+ if async_:
966
+ yield async_sleep(interval)
967
+ else:
968
+ sleep(interval)
969
+ running = True
970
+ if async_:
971
+ @asynccontextmanager
972
+ async def actx():
973
+ nonlocal running
974
+ try:
975
+ task = create_task(run())
976
+ yield task
977
+ finally:
978
+ running = False
979
+ task.cancel()
980
+ if use_default_call:
981
+ print("\r\x1b[K", end="")
982
+ return actx()
983
+ else:
984
+ @contextmanager
985
+ def ctx():
986
+ nonlocal running
987
+ try:
988
+ yield start_new_thread(run, ())
989
+ finally:
990
+ running = False
991
+ if use_default_call:
992
+ print("\r\x1b[K", end="")
993
+ return ctx()
994
+
995
+
996
+ def gen_startup[**Args, G: Generator](func: Callable[Args, G], /):
997
+ def wrapper(*args: Args.args, **kwds: Args.kwargs) -> G:
998
+ gen = func(*args, **kwds)
999
+ next(gen)
1000
+ return gen
1001
+ return update_wrapper(wrapper, func)
1002
+
1003
+
1004
+ def async_gen_startup[**Args, G: AsyncGenerator](func: Callable[Args, G], /):
1005
+ async def wrapper(*args: Args.args, **kwds: Args.kwargs) -> G:
1006
+ gen = func(*args, **kwds)
1007
+ await anext(gen)
1008
+ return gen
1009
+ return update_wrapper(wrapper, func)
1010
+
1011
+
1012
+ def do_iter[T](
1013
+ func: Callable[[], T] | Iterable[T],
1014
+ /,
1015
+ sentinel=undefined,
1016
+ sentinel_excs: type[BaseException] | tuple[type[BaseException], ...] = (),
1017
+ ) -> Iterator[T]:
1018
+ try:
1019
+ yield from iter(func if callable(func) else iter(func).__next__, sentinel)
1020
+ except sentinel_excs:
1021
+ pass
1022
+
1023
+
1024
+ async def do_aiter[T](
1025
+ func: Callable[[], T] | Callable[[], Awaitable[T]] | Iterable[T] | AsyncIterable[T],
1026
+ /,
1027
+ sentinel=undefined,
1028
+ sentinel_excs: type[BaseException] | tuple[type[BaseException], ...] = (),
1029
+ ) -> AsyncIterator[T]:
1030
+ if callable(func):
1031
+ func = ensure_async(func)
1032
+ else:
1033
+ func = ensure_aiter(func).__anext__
1034
+ try:
1035
+ while True:
1036
+ v = await func()
1037
+ if v is sentinel:
1038
+ break
1039
+ yield v
1040
+ except StopAsyncIteration:
1041
+ pass
1042
+ except sentinel_excs:
1043
+ pass
1044
+
1045
+
1046
+ def bfs_iter[T](*initials: T) -> tuple[Iterator[T], Callable[[T], None]]:
1047
+ dq = deque(initials)
1048
+ return do_iter(dq.popleft, sentinel_excs=IndexError), dq.append
1049
+
1050
+
1051
+ @gen_startup
1052
+ def bfs_gen[T](*initials) -> Generator[None | T, T | None, None]:
1053
+ dq = deque(initials)
1054
+ push, pop = dq.append, dq.popleft
1055
+ try:
1056
+ p = yield None
1057
+ while True:
1058
+ p = yield (pop() if p is None else push(p))
1059
+ except IndexError:
1060
+ pass
1061
+
1062
+ # TODO: 这个模块添加了很多不必要的函数,需要进行移除