python-iterutils 0.2.3.1__tar.gz → 0.2.4.1__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-iterutils
3
- Version: 0.2.3.1
3
+ Version: 0.2.4.1
4
4
  Summary: Python another itertools.
5
5
  Home-page: https://github.com/ChenyangGao/web-mount-packs/tree/main/python-module/python-iterutils
6
6
  License: MIT
@@ -21,7 +21,6 @@ Classifier: Topic :: Software Development
21
21
  Classifier: Topic :: Software Development :: Libraries
22
22
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
23
  Requires-Dist: python-asynctools (>=0.1.2)
24
- Requires-Dist: python-decotools (>=0.0.2)
25
24
  Requires-Dist: python-texttools (>=0.0.3)
26
25
  Requires-Dist: python-undefined (>=0.0.3)
27
26
  Project-URL: Repository, https://github.com/ChenyangGao/web-mount-packs/tree/main/python-module/python-iterutils
@@ -2,21 +2,20 @@
2
2
  # encoding: utf-8
3
3
 
4
4
  __author__ = "ChenyangGao <https://chenyanggao.github.io>"
5
- __version__ = (0, 2, 3)
5
+ __version__ = (0, 2, 4)
6
6
  __all__ = [
7
- "Return", "Yield", "YieldFrom", "iterable", "async_iterable",
8
- "foreach", "async_foreach", "through", "async_through", "flatten",
9
- "async_flatten", "collect", "async_collect", "group_collect",
10
- "async_group_collect", "map", "filter", "reduce", "zip", "chunked",
11
- "iter_unique", "async_iter_unique", "wrap_iter", "wrap_aiter",
12
- "acc_step", "cut_iter", "iter_gen_step", "iter_gen_step_async",
13
- "run_gen_step", "run_gen_step_sync_iter", "run_gen_step_async_iter",
14
- "run_gen_step_iter", "as_gen_step", "as_gen_step_iter", "bfs_gen",
15
- "with_iter_next", "backgroud_loop",
7
+ "Yield", "YieldFrom", "iterable", "async_iterable",
8
+ "run_gen_step", "run_gen_step_iter", "as_gen_step",
9
+ "as_gen_step_iter", "split_cm, ""with_iter_next",
10
+ "map", "filter", "reduce", "zip", "chunked",
11
+ "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", "acc_step",
15
+ "cut_iter", "bfs_gen", "context", "backgroud_loop",
16
16
  ]
17
17
 
18
- from abc import ABC, abstractmethod
19
- from asyncio import create_task, sleep as async_sleep, to_thread
18
+ from asyncio import create_task, sleep as async_sleep
20
19
  from builtins import map as _map, filter as _filter, zip as _zip
21
20
  from collections import defaultdict, deque
22
21
  from collections.abc import (
@@ -27,95 +26,481 @@ from collections.abc import (
27
26
  )
28
27
  from contextlib import (
29
28
  asynccontextmanager, contextmanager, ExitStack, AsyncExitStack,
29
+ AbstractContextManager, AbstractAsyncContextManager,
30
30
  )
31
31
  from copy import copy
32
32
  from dataclasses import dataclass
33
33
  from itertools import batched, pairwise
34
- from inspect import isawaitable, iscoroutinefunction
35
- from sys import _getframe
34
+ from inspect import isawaitable, iscoroutinefunction, signature
35
+ from sys import _getframe, exc_info
36
36
  from _thread import start_new_thread
37
37
  from time import sleep, time
38
38
  from types import FrameType
39
39
  from typing import (
40
40
  cast, overload, Any, AsyncContextManager, ContextManager, Literal,
41
- Protocol,
42
41
  )
43
42
 
44
43
  from asynctools import (
45
44
  async_filter, async_map, async_reduce, async_zip, async_batched,
46
45
  ensure_async, ensure_aiter, collect as async_collect,
47
46
  )
48
- from decotools import optional
49
47
  from texttools import format_time
50
48
  from undefined import undefined
51
49
 
52
50
 
53
- class SupportsBool(Protocol):
51
+ @dataclass(slots=True, frozen=True, unsafe_hash=True)
52
+ class Yield:
53
+ """专供 `run_gen_step_iter`,说明值需要 yield 给用户
54
54
  """
55
+ value: Any
56
+
57
+
58
+ @dataclass(slots=True, frozen=True, unsafe_hash=True)
59
+ class YieldFrom:
60
+ """专供 `run_gen_step_iter`,说明值需要解包后逐个 yield 给用户
55
61
  """
56
- def __bool__(self, /) -> bool: ...
62
+ value: Any
57
63
 
58
64
 
59
- class Reraised(BaseException):
65
+ def iterable(obj, /) -> bool:
66
+ """判断对象是不是 Iterable
60
67
  """
68
+ return isinstance(obj, Iterable)
69
+
70
+
71
+ def async_iterable(obj, /) -> bool:
72
+ """判断对象是不是 AsyncIterable
61
73
  """
62
- def __init__(self, exc: BaseException, /):
63
- if isinstance(exc, Reraised):
64
- exc = exc.exception
65
- self.exception: BaseException = exc
74
+ return isinstance(obj, AsyncIterable)
66
75
 
67
76
 
68
- @dataclass(slots=True, frozen=True, unsafe_hash=True)
69
- class YieldBase(ABC):
77
+ def _get_async(back: int = 2, /) -> bool:
78
+ """往上查找,从最近的调用栈的命名空间中获取 `async_` 的值
70
79
  """
80
+ f: None | FrameType
81
+ f = _getframe(back)
82
+ f_globals = f.f_globals
83
+ f_locals = f.f_locals
84
+ if f_locals is f_globals:
85
+ return f_locals.get("async_") or False
86
+ while f_locals is not None and f_locals is not f_globals:
87
+ if "async_" in f_locals:
88
+ if f_locals["async_"] is not None:
89
+ return f_locals["async_"]
90
+ f = f.f_back
91
+ if f is None:
92
+ break
93
+ f_locals = f.f_locals
94
+ return False
95
+
96
+
97
+ def _run_gen_step(gen: Generator, /):
98
+ send = gen.send
99
+ try:
100
+ value: Any = send(None)
101
+ while True:
102
+ value = send(value)
103
+ except StopIteration as e:
104
+ return e.value
105
+ finally:
106
+ gen.close()
107
+
108
+
109
+ async def _run_gen_step_async(gen: Generator, /):
110
+ send = gen.send
111
+ throw = gen.throw
112
+ try:
113
+ ret: Awaitable = send(None)
114
+ while True:
115
+ try:
116
+ value: Any = await ret
117
+ except BaseException as e:
118
+ ret = throw(e)
119
+ else:
120
+ ret = send(value)
121
+ except StopIteration as e:
122
+ if isawaitable(e.value):
123
+ return await e.value
124
+ return e.value
125
+ finally:
126
+ gen.close()
127
+
128
+
129
+ def run_gen_step[**Args](
130
+ gen_step: Generator | Callable[Args, Generator],
131
+ async_: None | Literal[False, True] = None,
132
+ /,
133
+ *args: Args.args,
134
+ **kwds: Args.kwargs,
135
+ ):
136
+ """驱动生成器运行,并返回其结果
71
137
  """
72
- value: Any
73
- may_await: None | bool | Literal[1] = False
74
- may_call: None | bool | Literal[1] = None
138
+ if async_ is None:
139
+ async_ = _get_async()
140
+ if not isinstance(gen_step, Generator):
141
+ params = signature(gen_step).parameters
142
+ if ((param := params.get("async_")) and
143
+ (param.kind in (param.POSITIONAL_OR_KEYWORD, param.KEYWORD_ONLY))
144
+ ):
145
+ kwds.setdefault("async_", async_)
146
+ gen_step = gen_step(*args, **kwds)
147
+ if async_:
148
+ return _run_gen_step_async(gen_step)
149
+ else:
150
+ return _run_gen_step(gen_step)
151
+
152
+
153
+ def _run_gen_step_iter(gen: Generator, /) -> Iterator:
154
+ send = gen.send
155
+ try:
156
+ value: Any = send(None)
157
+ while True:
158
+ if isinstance(value, Yield):
159
+ yield value.value
160
+ elif isinstance(value, YieldFrom):
161
+ yield from value.value
162
+ value = send(value)
163
+ except StopIteration as e:
164
+ value = e.value
165
+ if isinstance(value, Yield):
166
+ yield value.value
167
+ elif isinstance(value, YieldFrom):
168
+ yield from value.value
169
+ finally:
170
+ gen.close()
171
+
172
+
173
+ async def _run_gen_step_async_iter(gen: Generator, /) -> AsyncIterator:
174
+ send = gen.send
175
+ throw = gen.throw
176
+ try:
177
+ ret: Awaitable | Yield | YieldFrom = send(None)
178
+ while True:
179
+ try:
180
+ if isinstance(ret, Awaitable):
181
+ value: Any = await ret
182
+ else:
183
+ value = ret.value
184
+ if isawaitable(value):
185
+ value = await value
186
+ if isinstance(ret, Yield):
187
+ yield value
188
+ elif isinstance(value, AsyncIterable):
189
+ async for e in value:
190
+ yield e
191
+ else:
192
+ for e in value:
193
+ yield e
194
+ except BaseException as e:
195
+ ret = throw(e)
196
+ else:
197
+ ret = send(value)
198
+ except StopIteration as e:
199
+ val = e.value
200
+ if isinstance(val, (Yield, YieldFrom)):
201
+ value = val.value
202
+ if isawaitable(value):
203
+ value = await value
204
+ if isinstance(ret, Yield):
205
+ yield value
206
+ elif isinstance(value, AsyncIterable):
207
+ async for e in value:
208
+ yield e
209
+ else:
210
+ for e in value:
211
+ yield e
212
+ elif isawaitable(val):
213
+ await val
214
+ finally:
215
+ gen.close()
216
+
217
+
218
+ @overload
219
+ def run_gen_step_iter[**Args](
220
+ gen_step: Generator | Callable[Args, Generator],
221
+ async_: None = None,
222
+ /,
223
+ *args: Args.args,
224
+ **kwds: Args.kwargs,
225
+ ) -> Iterator | AsyncIterator:
226
+ ...
227
+ @overload
228
+ def run_gen_step_iter[**Args](
229
+ gen_step: Generator | Callable[Args, Generator],
230
+ async_: Literal[False] = False,
231
+ /,
232
+ *args: Args.args,
233
+ **kwds: Args.kwargs,
234
+ ) -> Iterator:
235
+ ...
236
+ @overload
237
+ def run_gen_step_iter[**Args](
238
+ gen_step: Generator | Callable[Args, Generator],
239
+ async_: Literal[True],
240
+ /,
241
+ *args: Args.args,
242
+ **kwds: Args.kwargs,
243
+ ) -> AsyncIterator:
244
+ ...
245
+ def run_gen_step_iter[**Args](
246
+ gen_step: Generator | Callable[Args, Generator],
247
+ async_: None | Literal[False, True] = None,
248
+ /,
249
+ *args: Args.args,
250
+ **kwds: Args.kwargs,
251
+ ) -> Iterator | AsyncIterator:
252
+ """驱动生成器运行,并从中返回可迭代而出的值
253
+ """
254
+ if async_ is None:
255
+ async_ = _get_async()
256
+ if not isinstance(gen_step, Generator):
257
+ params = signature(gen_step).parameters
258
+ if ((param := params.get("async_")) and
259
+ (param.kind in (param.POSITIONAL_OR_KEYWORD, param.KEYWORD_ONLY))
260
+ ):
261
+ kwds.setdefault("async_", async_)
262
+ gen_step = gen_step(*args, **kwds)
263
+ if async_:
264
+ return _run_gen_step_async_iter(gen_step)
265
+ else:
266
+ return _run_gen_step_iter(gen_step)
267
+
268
+
269
+ def as_gen_step[**Args](
270
+ gen_step: Callable[Args, Generator],
271
+ /,
272
+ ) -> Callable[Args, Any]:
273
+ def wrapper(*args: Args.args, **kwds: Args.kwargs):
274
+ return run_gen_step(
275
+ gen_step,
276
+ cast(None | Literal[False, True], kwds.pop("async_", None)),
277
+ *args,
278
+ **kwds,
279
+ )
280
+ return wrapper
281
+
282
+
283
+ def as_gen_step_iter[**Args](
284
+ gen_step: Callable[Args, Generator],
285
+ /,
286
+ ) -> Callable[Args, Iterable | AsyncIterable]:
287
+ def wrapper(*args: Args.args, **kwds: Args.kwargs):
288
+ return run_gen_step_iter(
289
+ gen_step,
290
+ cast(None | Literal[False, True], kwds.pop("async_", None)),
291
+ *args,
292
+ **kwds,
293
+ )
294
+ return wrapper
295
+
296
+
297
+ @overload
298
+ def split_cm[T](
299
+ cm: AbstractContextManager[T],
300
+ /,
301
+ ) -> tuple[Callable[[], T], Callable[[], Any]]:
302
+ ...
303
+ @overload
304
+ def split_cm[T](
305
+ cm: AbstractAsyncContextManager[T],
306
+ /,
307
+ ) -> tuple[Callable[[], Coroutine[Any, Any, T]], Callable[[], Coroutine]]:
308
+ ...
309
+ def split_cm[T](
310
+ cm: AbstractContextManager[T] | AbstractAsyncContextManager[T],
311
+ /,
312
+ ) -> (
313
+ tuple[Callable[[], T], Callable[[], Any]] |
314
+ tuple[Callable[[], Coroutine[Any, Any, T]], Callable[[], Coroutine]]
315
+ ):
316
+ """拆分上下文管理器,以供 `run_gen_step` 和 `run_gen_step_iter` 使用
317
+
318
+ .. code:: python
319
+
320
+ if async_:
321
+ async def process():
322
+ async with cm as obj:
323
+ do_what_you_want()
324
+ return process()
325
+ else:
326
+ with cm as obj:
327
+ do_what_you_want()
328
+
329
+ 大概相当于
330
+
331
+ .. code:: python
332
+
333
+ def gen_step():
334
+ enter, exit = split_cm(cm)
335
+ obj = yield enter()
336
+ try:
337
+ do_what_you_want()
338
+ finally:
339
+ yield exit()
340
+
341
+ run_gen_step(gen_step, async_=async_)
342
+ """
343
+ if isinstance(cm, AbstractAsyncContextManager):
344
+ enter: Callable = cm.__aenter__
345
+ exit: Callable = cm.__aexit__
346
+ else:
347
+ enter = cm.__enter__
348
+ exit = cm.__exit__
349
+ return enter, lambda: exit(*exc_info())
350
+
351
+
352
+ @overload
353
+ def with_iter_next[T](
354
+ iterable: Iterable[T],
355
+ /,
356
+ ) -> ContextManager[Callable[[], T]]:
357
+ ...
358
+ @overload
359
+ def with_iter_next[T](
360
+ iterable: AsyncIterable[T],
361
+ /,
362
+ ) -> ContextManager[Callable[[], Awaitable[T]]]:
363
+ ...
364
+ @contextmanager
365
+ def with_iter_next[T](
366
+ iterable: Iterable[T] | AsyncIterable[T],
367
+ /,
368
+ ):
369
+ """包装迭代器,以供 `run_gen_step` 和 `run_gen_step_iter` 使用
370
+
371
+ .. code:: python
75
372
 
76
- @property
77
- @abstractmethod
78
- def yield_type(self, /) -> int:
79
- ...
373
+ if async_:
374
+ async def process():
375
+ async for e in iterable:
376
+ do_what_you_want()
377
+ return process()
378
+ else:
379
+ for e in iterable:
380
+ do_what_you_want()
381
+
382
+ 大概相当于
383
+
384
+ .. code:: python
385
+
386
+ def gen_step():
387
+ with with_iter_next(iterable) as do_next:
388
+ while True:
389
+ e = yield do_next()
390
+ do_what_you_want()
391
+
392
+ run_gen_step(gen_step, async_=async_)
393
+ """
394
+ if isinstance(iterable, AsyncIterable):
395
+ try:
396
+ yield aiter(iterable).__anext__
397
+ except StopAsyncIteration:
398
+ pass
399
+ else:
400
+ try:
401
+ yield iter(iterable).__next__
402
+ except StopIteration:
403
+ pass
80
404
 
81
405
 
82
- class Return(YieldBase):
406
+ def map(
407
+ function: None | Callable,
408
+ iterable: Iterable | AsyncIterable,
409
+ /,
410
+ *iterables: Iterable | AsyncIterable,
411
+ ):
83
412
  """
84
413
  """
85
- __slots__ = ()
86
- yield_type = 0
414
+ if (
415
+ iscoroutinefunction(function) or
416
+ isinstance(iterable, AsyncIterable) or
417
+ any(isinstance(i, AsyncIterable) for i in iterables)
418
+ ):
419
+ if function is None:
420
+ if iterables:
421
+ return async_zip(iterable, *iterables)
422
+ else:
423
+ return iterable
424
+ return async_map(function, iterable, *iterables)
425
+ if function is None:
426
+ if iterables:
427
+ return _zip(iterable, *iterables)
428
+ else:
429
+ return iterable
430
+ return _map(function, iterable, *iterables)
87
431
 
88
432
 
89
- class Yield(YieldBase):
433
+ def filter(
434
+ function: None | Callable,
435
+ iterable: Iterable | AsyncIterable,
436
+ /,
437
+ ):
90
438
  """
91
439
  """
92
- __slots__ = ()
93
- yield_type = 1
440
+ if iscoroutinefunction(function) or isinstance(iterable, AsyncIterable):
441
+ return async_filter(function, iterable)
442
+ return _filter(function, iterable)
94
443
 
95
444
 
96
- class YieldFrom(YieldBase):
445
+ def reduce(
446
+ function: Callable,
447
+ iterable: Iterable | AsyncIterable,
448
+ initial: Any = undefined,
449
+ /,
450
+ ):
97
451
  """
98
452
  """
99
- __slots__ = ()
100
- yield_type = 2
453
+ if iscoroutinefunction(function) or isinstance(iterable, AsyncIterable):
454
+ return async_reduce(function, iterable, initial)
455
+ from functools import reduce
456
+ if initial is undefined:
457
+ return reduce(function, iterable)
458
+ return reduce(function, iterable, initial)
101
459
 
102
460
 
103
- def iterable(iterable, /) -> bool:
461
+ def zip(
462
+ iterable: Iterable | AsyncIterable,
463
+ /,
464
+ *iterables: Iterable | AsyncIterable,
465
+ ):
104
466
  """
105
467
  """
106
- try:
107
- return isinstance(iter(iterable), Iterable)
108
- except TypeError:
109
- return False
468
+ if isinstance(iterable, AsyncIterable) or any(isinstance(i, AsyncIterable) for i in iterables):
469
+ return async_zip(iterable, *iterables)
470
+ return _zip(iterable, *iterables)
110
471
 
111
472
 
112
- def async_iterable(iterable, /) -> bool:
473
+ @overload
474
+ def chunked[T](
475
+ iterable: Iterable[T],
476
+ n: int = 1,
477
+ /,
478
+ ) -> Iterator[Sequence[T]]:
479
+ ...
480
+ @overload
481
+ def chunked[T](
482
+ iterable: AsyncIterable[T],
483
+ n: int = 1,
484
+ /,
485
+ ) -> AsyncIterator[Sequence[T]]:
486
+ ...
487
+ def chunked[T](
488
+ iterable: Iterable[T] | AsyncIterable[T],
489
+ n: int = 1,
490
+ /,
491
+ ) -> Iterator[Sequence[T]] | AsyncIterator[Sequence[T]]:
113
492
  """
114
493
  """
115
- try:
116
- return isinstance(iter(iterable), AsyncIterable)
117
- except TypeError:
118
- return False
494
+ if n < 0:
495
+ n = 1
496
+ if isinstance(iterable, Sequence):
497
+ if n == 1:
498
+ return ((e,) for e in iterable)
499
+ return (iterable[i:j] for i, j in pairwise(range(0, len(iterable)+n, n)))
500
+ elif isinstance(iterable, Iterable):
501
+ return batched(iterable, n)
502
+ else:
503
+ return async_batched(iterable, n)
119
504
 
120
505
 
121
506
  def foreach(
@@ -410,109 +795,9 @@ async def async_group_collect[K, V, C: Container, M: MutableMapping](
410
795
  return mapping
411
796
 
412
797
 
413
- def map(
414
- function: None | Callable,
415
- iterable: Iterable | AsyncIterable,
416
- /,
417
- *iterables: Iterable | AsyncIterable,
418
- ):
419
- """
420
- """
421
- if (
422
- iscoroutinefunction(function) or
423
- isinstance(iterable, AsyncIterable) or
424
- any(isinstance(i, AsyncIterable) for i in iterables)
425
- ):
426
- if function is None:
427
- if iterables:
428
- return async_zip(iterable, *iterables)
429
- else:
430
- return iterable
431
- return async_map(function, iterable, *iterables)
432
- if function is None:
433
- if iterables:
434
- return _zip(iterable, *iterables)
435
- else:
436
- return iterable
437
- return _map(function, iterable, *iterables)
438
-
439
-
440
- def filter(
441
- function: None | Callable,
442
- iterable: Iterable | AsyncIterable,
443
- /,
444
- ):
445
- """
446
- """
447
- if iscoroutinefunction(function) or isinstance(iterable, AsyncIterable):
448
- return async_filter(function, iterable)
449
- return _filter(function, iterable)
450
-
451
-
452
- def reduce(
453
- function: Callable,
454
- iterable: Iterable | AsyncIterable,
455
- initial: Any = undefined,
456
- /,
457
- ):
458
- """
459
- """
460
- if iscoroutinefunction(function) or isinstance(iterable, AsyncIterable):
461
- return async_reduce(function, iterable, initial)
462
- from functools import reduce
463
- if initial is undefined:
464
- return reduce(function, iterable)
465
- return reduce(function, iterable, initial)
466
-
467
-
468
- def zip(
469
- iterable: Iterable | AsyncIterable,
470
- /,
471
- *iterables: Iterable | AsyncIterable,
472
- ):
473
- """
474
- """
475
- if isinstance(iterable, AsyncIterable) or any(isinstance(i, AsyncIterable) for i in iterables):
476
- return async_zip(iterable, *iterables)
477
- return _zip(iterable, *iterables)
478
-
479
-
480
- @overload
481
- def chunked[T](
482
- iterable: Iterable[T],
483
- n: int = 1,
484
- /,
485
- ) -> Iterator[Sequence[T]]:
486
- ...
487
- @overload
488
- def chunked[T](
489
- iterable: AsyncIterable[T],
490
- n: int = 1,
491
- /,
492
- ) -> AsyncIterator[Sequence[T]]:
493
- ...
494
- def chunked[T](
495
- iterable: Iterable[T] | AsyncIterable[T],
496
- n: int = 1,
497
- /,
498
- ) -> Iterator[Sequence[T]] | AsyncIterator[Sequence[T]]:
499
- """
500
- """
501
- if n < 0:
502
- n = 1
503
- if isinstance(iterable, Sequence):
504
- if n == 1:
505
- return ((e,) for e in iterable)
506
- return (iterable[i:j] for i, j in pairwise(range(0, len(iterable)+n, n)))
507
- elif isinstance(iterable, Iterable):
508
- return batched(iterable, n)
509
- else:
510
- return async_batched(iterable, n)
511
-
512
-
513
- @overload
514
- def iter_unique[T](
515
- iterable: Iterable[T],
798
+ @overload
799
+ def iter_unique[T](
800
+ iterable: Iterable[T],
516
801
  /,
517
802
  seen: None | MutableSet = None,
518
803
  ) -> Iterator[T]:
@@ -650,460 +935,6 @@ def cut_iter(
650
935
  yield stop, stop - start
651
936
 
652
937
 
653
- def _get_async(back: int = 2) -> bool:
654
- f: None | FrameType
655
- f = _getframe(back)
656
- f_globals = f.f_globals
657
- f_locals = f.f_locals
658
- if f_locals is f_globals:
659
- return f_locals.get("async_") or False
660
- while f_locals is not None and f_locals is not f_globals:
661
- if "async_" in f_locals:
662
- return f_locals["async_"] or False
663
- f = f.f_back
664
- if f is None:
665
- break
666
- f_locals = f.f_locals
667
- return False
668
-
669
-
670
- @overload
671
- def call_as_async[**Args, T: Coroutine](
672
- func: Callable[Args, T], /,
673
- may_await: bool | Literal[1] = False,
674
- threaded: bool = False,
675
- ) -> Callable[Args, T]:
676
- ...
677
- @overload
678
- def call_as_async[**Args, T](
679
- func: Callable[Args, T], /,
680
- may_await: bool | Literal[1] = False,
681
- threaded: bool = False,
682
- ) -> Callable[Args, Coroutine[Any, Any, T]]:
683
- ...
684
- def call_as_async[**Args, T](
685
- func: Callable[Args, T],
686
- /,
687
- may_await: bool | Literal[1] = False,
688
- threaded: bool = False,
689
- ) -> Callable[Args, T] | Callable[Args, Coroutine[Any, Any, T]]:
690
- """
691
- """
692
- if iscoroutinefunction(func):
693
- return func
694
- def wraps(*args, **kwds):
695
- try:
696
- return func(*args, **kwds)
697
- except (StopIteration, StopAsyncIteration) as e:
698
- raise Reraised(e) from e
699
- async def wrapper(*args, **kwds):
700
- if threaded:
701
- value = await to_thread(wraps, *args, **kwds)
702
- else:
703
- value = wraps(*args, **kwds)
704
- if may_await is 1 or may_await and isawaitable(value):
705
- value = await value
706
- return value
707
- return wrapper
708
-
709
-
710
- def iter_gen_step(
711
- gen_step: Generator | Callable[[], Generator],
712
- may_call: bool | Literal[1] = True,
713
- ):
714
- """
715
- """
716
- if callable(gen_step):
717
- gen_step = gen_step()
718
- send = gen_step.send
719
- throw = gen_step.throw
720
- close = gen_step.close
721
- value: Any = None
722
- try:
723
- while True:
724
- if isinstance(value, YieldBase):
725
- raise StopIteration(value)
726
- try:
727
- if may_call is 1 or may_call and callable(value):
728
- value = value()
729
- except BaseException as e:
730
- value = throw(e)
731
- else:
732
- value = send(value)
733
- yield value
734
- except StopIteration as e:
735
- value = e.value
736
- if isinstance(value, YieldBase):
737
- maybe_callable = value.may_call
738
- if maybe_callable is not None:
739
- may_call = maybe_callable
740
- value = value.value
741
- if may_call is 1 or may_call and callable(value):
742
- try:
743
- value = value()
744
- except BaseException as e:
745
- try:
746
- value = throw(e)
747
- except BaseException as e:
748
- raise Reraised(e) from e
749
- yield value
750
- finally:
751
- close()
752
-
753
-
754
- async def iter_gen_step_async(
755
- gen_step: Generator | Callable[[], Generator],
756
- may_await: bool | Literal[1] = True,
757
- may_call: bool | Literal[1] = True,
758
- threaded: bool = False,
759
- ):
760
- """
761
- """
762
- if callable(gen_step):
763
- gen_step = gen_step()
764
- send: Callable = call_as_async(gen_step.send, threaded=threaded)
765
- throw: Callable = call_as_async(gen_step.throw, threaded=threaded)
766
- close: Callable = call_as_async(gen_step.close, threaded=threaded)
767
- value: Any = None
768
- try:
769
- while True:
770
- if isinstance(value, YieldBase):
771
- raise StopIteration(value)
772
- try:
773
- if may_call is 1 or may_call and callable(value):
774
- value = await call_as_async(
775
- value, may_await=may_await, threaded=threaded)()
776
- elif may_await is 1 or may_await and isawaitable(value):
777
- value = await value
778
- except BaseException as e:
779
- if isinstance(e, Reraised):
780
- e = e.exception
781
- value = await throw(e)
782
- else:
783
- value = await send(value)
784
- yield value
785
- except BaseException as e:
786
- if isinstance(e, Reraised):
787
- e = e.exception
788
- if isinstance(e, StopIteration):
789
- value = e.value
790
- if isinstance(value, YieldBase):
791
- maybe_awaitable = value.may_await
792
- if maybe_awaitable is not None:
793
- may_await = maybe_awaitable
794
- maybe_callable = value.may_call
795
- if maybe_callable is not None:
796
- may_call = maybe_callable
797
- value = value.value
798
- try:
799
- if may_call is 1 or may_call and callable(value):
800
- value = await call_as_async(
801
- value, may_await=may_await, threaded=threaded)()
802
- elif may_await is 1 or may_await and isawaitable(value):
803
- value = await value
804
- except BaseException as e:
805
- try:
806
- value = await throw(e)
807
- except BaseException as e:
808
- raise Reraised(e) from e
809
- yield value
810
- else:
811
- raise
812
- finally:
813
- await close()
814
-
815
-
816
- def run_gen_step(
817
- gen_step: Generator | Callable[[], Generator],
818
- may_await: bool | Literal[1] = True,
819
- may_call: bool | Literal[1] = True,
820
- threaded: bool = False,
821
- running_flag: None | SupportsBool | Callable[[], SupportsBool] | Callable[[], Awaitable[SupportsBool]] = None,
822
- *,
823
- async_: None | Literal[False, True] = None,
824
- ):
825
- """
826
- """
827
- if async_ is None:
828
- async_ = _get_async()
829
- if async_:
830
- async def process():
831
- gen = iter_gen_step_async(
832
- gen_step,
833
- may_await=may_await,
834
- may_call=may_call,
835
- threaded=threaded,
836
- )
837
- try:
838
- if running_flag is None:
839
- async for value in gen:
840
- pass
841
- elif callable(running_flag):
842
- pred = call_as_async(running_flag, threaded=threaded)
843
- if not await pred():
844
- raise RuntimeError("stop before starting", gen) from StopIteration()
845
- async for value in gen:
846
- if not await pred():
847
- raise RuntimeError("stop midway", gen) from StopIteration(value)
848
- else:
849
- if not running_flag:
850
- raise RuntimeError("stop before starting", gen) from StopIteration()
851
- async for value in gen:
852
- if not running_flag:
853
- raise RuntimeError("stop midway", gen) from StopIteration(value)
854
- return value
855
- except Reraised as e:
856
- raise e.exception
857
- except KeyboardInterrupt as e:
858
- e.args = gen,
859
- e.add_note(f"stop iterating gen_step: {gen!r}")
860
- raise
861
- return process()
862
- else:
863
- gen = iter_gen_step(gen_step, may_call=may_call)
864
- try:
865
- if running_flag is None:
866
- for value in gen:
867
- pass
868
- else:
869
- if not callable(running_flag):
870
- running_flag = running_flag.__bool__
871
- if not running_flag():
872
- raise RuntimeError("stop before starting", gen) from StopIteration()
873
- for value in gen:
874
- if not running_flag():
875
- raise RuntimeError("stop midway", gen) from StopIteration(value)
876
- return value
877
- except Reraised as e:
878
- raise e.exception
879
- except KeyboardInterrupt as e:
880
- e.args = gen,
881
- e.add_note(f"stop iterating gen_step: {gen!r}")
882
- raise
883
-
884
-
885
- def run_gen_step_sync_iter(
886
- gen_step: Generator | Callable[[], Generator],
887
- may_call: bool | Literal[1] = True,
888
- ) -> Iterator:
889
- """
890
- """
891
- if callable(gen_step):
892
- gen_step = gen_step()
893
- send: Callable = gen_step.send
894
- throw: Callable = gen_step.throw
895
- close: Callable = gen_step.close
896
- def extract(value, may_call=may_call, /):
897
- yield_type = -1
898
- if isinstance(value, YieldBase):
899
- yield_type = value.yield_type
900
- maybe_callable = value.may_call
901
- if maybe_callable is not None:
902
- may_call = maybe_callable
903
- value = value.value
904
- if may_call is 1 or may_call and callable(value):
905
- value = value()
906
- return yield_type, value
907
- try:
908
- value = send(None)
909
- while True:
910
- try:
911
- yield_type, value = extract(value)
912
- match yield_type:
913
- case 0:
914
- return value
915
- case 1:
916
- yield value
917
- case 2:
918
- yield from value
919
- except BaseException as e:
920
- value = throw(e)
921
- else:
922
- value = send(value)
923
- except StopIteration as e:
924
- try:
925
- yield_type, value = extract(e.value)
926
- match yield_type:
927
- case 1:
928
- yield value
929
- case 2:
930
- yield from value
931
- case _:
932
- return value
933
- except BaseException as e:
934
- throw(e)
935
- finally:
936
- close()
937
-
938
-
939
- async def run_gen_step_async_iter(
940
- gen_step: Generator | Callable[[], Generator],
941
- may_await: bool | Literal[1] = True,
942
- may_call: bool | Literal[1] = True,
943
- threaded: bool = False,
944
- ) -> AsyncIterator:
945
- """
946
- """
947
- if callable(gen_step):
948
- gen_step = gen_step()
949
- send: Callable = call_as_async(gen_step.send, threaded=threaded)
950
- throw: Callable = call_as_async(gen_step.throw, threaded=threaded)
951
- close: Callable = call_as_async(gen_step.close, threaded=threaded)
952
- async def extract(value, may_await=may_await, may_call=may_call, /):
953
- yield_type = -1
954
- if isinstance(value, YieldBase):
955
- yield_type = value.yield_type
956
- maybe_awaitable = value.may_await
957
- if maybe_awaitable is not None:
958
- may_await = maybe_awaitable
959
- maybe_callable = value.may_call
960
- if maybe_callable is not None:
961
- may_call = maybe_callable
962
- value = value.value
963
- if may_call is 1 or may_call and callable(value):
964
- value = await call_as_async(
965
- value, may_await=may_await, threaded=threaded)()
966
- elif may_await is 1 or may_await and isawaitable(value):
967
- value = await value
968
- return yield_type, value
969
- try:
970
- value = await send(None)
971
- while True:
972
- try:
973
- yield_type, value = await extract(value)
974
- match yield_type:
975
- case 0:
976
- return
977
- case 1:
978
- yield value
979
- case 2:
980
- async for val in ensure_aiter(value, threaded=threaded):
981
- yield val
982
- except BaseException as e:
983
- if isinstance(e, Reraised):
984
- e = e.exception
985
- value = await throw(e)
986
- else:
987
- value = await send(value)
988
- except BaseException as e:
989
- if isinstance(e, Reraised):
990
- e = e.exception
991
- if isinstance(e, StopIteration):
992
- try:
993
- yield_type, value = await extract(e.value)
994
- match yield_type:
995
- case 1:
996
- yield value
997
- case 2:
998
- async for val in ensure_aiter(value, threaded=threaded):
999
- yield val
1000
- except BaseException as e:
1001
- if isinstance(e, Reraised):
1002
- e = e.exception
1003
- await throw(e)
1004
- else:
1005
- raise
1006
- finally:
1007
- await close()
1008
-
1009
-
1010
- @overload
1011
- def run_gen_step_iter(
1012
- gen_step: Generator | Callable[[], Generator],
1013
- may_await: bool | Literal[1] = True,
1014
- may_call: bool | Literal[1] = True,
1015
- threaded: bool = False,
1016
- running_flag: None | SupportsBool | Callable[[], SupportsBool] | Callable[[], Awaitable[SupportsBool]] = None,
1017
- *,
1018
- async_: None = None,
1019
- ) -> Iterator | AsyncIterator:
1020
- ...
1021
- @overload
1022
- def run_gen_step_iter(
1023
- gen_step: Generator | Callable[[], Generator],
1024
- may_await: bool | Literal[1] = True,
1025
- may_call: bool | Literal[1] = True,
1026
- threaded: bool = False,
1027
- running_flag: None | SupportsBool | Callable[[], SupportsBool] | Callable[[], Awaitable[SupportsBool]] = None,
1028
- *,
1029
- async_: Literal[False],
1030
- ) -> Iterator:
1031
- ...
1032
- @overload
1033
- def run_gen_step_iter(
1034
- gen_step: Generator | Callable[[], Generator],
1035
- may_await: bool | Literal[1] = True,
1036
- may_call: bool | Literal[1] = True,
1037
- threaded: bool = False,
1038
- running_flag: None | SupportsBool | Callable[[], SupportsBool] | Callable[[], Awaitable[SupportsBool]] = None,
1039
- *,
1040
- async_: Literal[True],
1041
- ) -> AsyncIterator:
1042
- ...
1043
- def run_gen_step_iter(
1044
- gen_step: Generator | Callable[[], Generator],
1045
- may_await: bool | Literal[1] = True,
1046
- may_call: bool | Literal[1] = True,
1047
- threaded: bool = False,
1048
- running_flag: None | SupportsBool | Callable[[], SupportsBool] | Callable[[], Awaitable[SupportsBool]] = None,
1049
- *,
1050
- async_: None | Literal[False, True] = None,
1051
- ) -> Iterator | AsyncIterator:
1052
- """
1053
- """
1054
- if async_ is None:
1055
- async_ = _get_async()
1056
- if async_:
1057
- return run_gen_step_async_iter(
1058
- gen_step,
1059
- may_await=may_await,
1060
- may_call=may_call,
1061
- threaded=threaded,
1062
- )
1063
- else:
1064
- return run_gen_step_sync_iter(gen_step, may_call=may_call)
1065
-
1066
-
1067
- @optional
1068
- def as_gen_step(
1069
- func: Callable[..., Generator],
1070
- /,
1071
- *deco_args,
1072
- iter: bool = False,
1073
- **deco_kwds,
1074
- ) -> Callable:
1075
- """装饰器,把生成器封装成 gen_step 函数
1076
- """
1077
- call = run_gen_step_iter if iter else run_gen_step
1078
- def wrapper(*args, async_: Literal[False, True] = False, **kwds):
1079
- return call(
1080
- func(*args, async_=async_, **kwds),
1081
- *deco_args,
1082
- async_=async_,
1083
- **deco_kwds,
1084
- )
1085
- return wrapper
1086
-
1087
-
1088
- @optional
1089
- def as_gen_step_iter(
1090
- func: Callable[..., Generator],
1091
- /,
1092
- *deco_args,
1093
- **deco_kwds,
1094
- ) -> Callable:
1095
- """装饰器,把生成器封装成 gen_step 函数
1096
- """
1097
- def wrapper(*args, async_: Literal[False, True] = False, **kwds):
1098
- return run_gen_step_iter(
1099
- func(*args, async_=async_, **kwds),
1100
- *deco_args,
1101
- async_=async_,
1102
- **deco_kwds,
1103
- )
1104
- return wrapper
1105
-
1106
-
1107
938
  @overload
1108
939
  def bfs_gen[T](
1109
940
  initial: T,
@@ -1146,59 +977,6 @@ def bfs_gen[T](
1146
977
  args = yield val
1147
978
 
1148
979
 
1149
- @overload
1150
- def with_iter_next[T](
1151
- iterable: Iterable[T],
1152
- /,
1153
- async_: Literal[False],
1154
- ) -> ContextManager[Callable[[], T]]:
1155
- ...
1156
- @overload
1157
- def with_iter_next[T](
1158
- iterable: Iterable[T] | AsyncIterable[T],
1159
- /,
1160
- async_: Literal[True],
1161
- ) -> ContextManager[Callable[[], Awaitable[T]]]:
1162
- ...
1163
- @overload
1164
- def with_iter_next[T](
1165
- iterable: Iterable[T] | AsyncIterable[T],
1166
- /,
1167
- async_: None = None,
1168
- ) -> ContextManager[Callable[[], T]] | ContextManager[Callable[[], Awaitable[T]]]:
1169
- ...
1170
- @contextmanager
1171
- def with_iter_next[T](
1172
- iterable: Iterable[T] | AsyncIterable[T],
1173
- /,
1174
- async_: None | Literal[False, True] = None,
1175
- ):
1176
- """
1177
- """
1178
- if async_ is None:
1179
- if not isinstance(iterable, Iterable):
1180
- async_ = True
1181
- else:
1182
- async_ = _get_async()
1183
- if async_:
1184
- get_next: Callable[[], T] | Callable[[], Awaitable[T]] = ensure_aiter(iterable).__anext__
1185
- elif isinstance(iterable, Iterable):
1186
- get_next = iter(iterable).__next__
1187
- else:
1188
- get_next = aiter(iterable).__anext__
1189
- async_ = True
1190
- if async_:
1191
- try:
1192
- yield get_next
1193
- except StopAsyncIteration:
1194
- pass
1195
- else:
1196
- try:
1197
- yield get_next
1198
- except StopIteration:
1199
- pass
1200
-
1201
-
1202
980
  @overload
1203
981
  def context[T](
1204
982
  func: Callable[..., T],
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "python-iterutils"
3
- version = "0.2.3.1"
3
+ version = "0.2.4.1"
4
4
  description = "Python another itertools."
5
5
  authors = ["ChenyangGao <wosiwujm@gmail.com>"]
6
6
  license = "MIT"
@@ -28,7 +28,6 @@ include = [
28
28
  [tool.poetry.dependencies]
29
29
  python = "^3.12"
30
30
  python-asynctools = ">=0.1.2"
31
- python-decotools = ">=0.0.2"
32
31
  python-texttools = ">=0.0.3"
33
32
  python-undefined = ">=0.0.3"
34
33