python-iterutils 0.2.10.1__tar.gz → 0.3.0__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,9 +1,9 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: python-iterutils
3
- Version: 0.2.10.1
3
+ Version: 0.3.0
4
4
  Summary: Python another itertools.
5
- Home-page: https://github.com/ChenyangGao/python-modules/tree/main/python-iterutils
6
5
  License: MIT
6
+ License-File: LICENSE
7
7
  Keywords: iterable,iterutils
8
8
  Author: ChenyangGao
9
9
  Author-email: wosiwujm@gmail.com
@@ -16,13 +16,15 @@ Classifier: Programming Language :: Python
16
16
  Classifier: Programming Language :: Python :: 3
17
17
  Classifier: Programming Language :: Python :: 3.12
18
18
  Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
19
20
  Classifier: Programming Language :: Python :: 3 :: Only
20
21
  Classifier: Topic :: Software Development
21
22
  Classifier: Topic :: Software Development :: Libraries
22
23
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
- Requires-Dist: python-asynctools (>=0.1.3.4)
24
- Requires-Dist: python-texttools (>=0.0.3)
25
- Requires-Dist: python-undefined (>=0.0.3)
24
+ Requires-Dist: python-asynctools (>=0.1.4)
25
+ Requires-Dist: python-texttools (>=0.0.6)
26
+ Requires-Dist: python-undefined (>=0.0.4)
27
+ Project-URL: Homepage, https://github.com/ChenyangGao/python-modules/tree/main/python-iterutils
26
28
  Project-URL: Repository, https://github.com/ChenyangGao/python-modules/tree/main/python-iterutils
27
29
  Description-Content-Type: text/markdown
28
30
 
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env python3
2
+ # encoding: utf-8
3
+
4
+ __author__ = "ChenyangGao <https://chenyanggao.github.io>"
5
+ __version__ = (0, 3, 0)
6
+
7
+ from .dynamic import *
8
+ from .gen_step import *
9
+ from .misc import *
@@ -0,0 +1,102 @@
1
+ #!/usr/bin/env python3
2
+ # encoding: utf-8
3
+
4
+ __all__ = ["dynamic_async", "dynamic_async_iter"]
5
+
6
+ from asyncio import run as asyncio_run, to_thread
7
+ from collections.abc import (
8
+ AsyncIterable, AsyncIterator, Awaitable, Callable, Iterable, Iterator,
9
+ )
10
+ from inspect import isawaitable
11
+
12
+ from argtools import has_keyword_arg
13
+ from asynctools import ensure_async, in_async
14
+ from decotools import optional
15
+
16
+
17
+ async def _as_async[T](v: T, /) -> T:
18
+ return v
19
+
20
+
21
+ async def _as_async_iter[T](
22
+ it: Iterable[T],
23
+ /,
24
+ threaded: bool = False,
25
+ ) -> AsyncIterator[T]:
26
+ if threaded:
27
+ call = iter(it).__next__
28
+ try:
29
+ while True:
30
+ yield await to_thread(call)
31
+ except StopIteration:
32
+ pass
33
+ else:
34
+ for e in it:
35
+ yield e
36
+
37
+
38
+ def _as_iter[T](
39
+ it: AsyncIterable[T],
40
+ /,
41
+ run: Callable = asyncio_run,
42
+ ) -> Iterator[T]:
43
+ try:
44
+ call = aiter(it).__anext__
45
+ while True:
46
+ yield run(call())
47
+ except StopAsyncIteration:
48
+ pass
49
+
50
+
51
+ @optional
52
+ def dynamic_async[T](
53
+ func: Callable,
54
+ /,
55
+ run: Callable = asyncio_run,
56
+ threaded: bool = False,
57
+ async_: None | bool = None,
58
+ ):
59
+ has_async_arg = has_keyword_arg(func, "async_")
60
+ def wrapper(*args, async_: None | bool = async_, **kwds):
61
+ if async_ is None:
62
+ async_ = in_async()
63
+ if has_async_arg:
64
+ kwds["async_"] = async_
65
+ if async_:
66
+ if threaded:
67
+ r = ensure_async(func, threaded=threaded)(*args, **kwds)
68
+ else:
69
+ r = func(*args, **kwds)
70
+ if not isawaitable(r):
71
+ r = _as_async(r)
72
+ else:
73
+ r = func(*args, **kwds)
74
+ if isawaitable(r):
75
+ r = run(r)
76
+ return r
77
+ return wrapper
78
+
79
+
80
+ @optional
81
+ def dynamic_async_iter[T](
82
+ func: Callable[..., Iterable] | Callable[..., AsyncIterable],
83
+ /,
84
+ run: Callable = asyncio_run,
85
+ threaded: bool = False,
86
+ async_: None | bool = None,
87
+ ):
88
+ has_async_arg = has_keyword_arg(func, "async_")
89
+ def wrapper(*args, async_: None | bool = async_, **kwds):
90
+ if async_ is None:
91
+ async_ = in_async()
92
+ if has_async_arg:
93
+ kwds["async_"] = async_
94
+ it = func(*args, **kwds)
95
+ if async_:
96
+ if not isinstance(it, AsyncIterable):
97
+ it = _as_async_iter(it, threaded=threaded)
98
+ elif isinstance(it, AsyncIterable):
99
+ it = _as_iter(it, run)
100
+ return it
101
+ return wrapper
102
+
@@ -0,0 +1,414 @@
1
+ #!/usr/bin/env python3
2
+ # encoding: utf-8
3
+
4
+ __all__ = [
5
+ "iter_gen_step", "run_gen_step", "run_gen_step_iter",
6
+ "as_gen_step", "with_iter_next", "Yield", "YieldFrom",
7
+ ]
8
+
9
+ from asyncio import CancelledError as AsyncCancelledError
10
+ from collections.abc import (
11
+ AsyncGenerator, AsyncIterable, AsyncIterator, Awaitable, Callable,
12
+ Coroutine, Generator, Iterable, Iterator,
13
+ )
14
+ from concurrent.futures import CancelledError
15
+ from contextlib import contextmanager, AbstractContextManager, AbstractAsyncContextManager
16
+ from dataclasses import dataclass
17
+ from inspect import isawaitable
18
+ from sys import exc_info
19
+ from typing import (
20
+ cast, overload, runtime_checkable, Any, ContextManager, Literal, Protocol,
21
+ )
22
+
23
+ from argtools import has_keyword_arg
24
+ from asynctools import in_async
25
+ from decotools import optional
26
+
27
+
28
+ @runtime_checkable
29
+ class SupportsBool(Protocol):
30
+ def __bool__(self, /) -> bool:
31
+ ...
32
+
33
+
34
+ @dataclass(slots=True, frozen=True)
35
+ class Yield:
36
+ """专供 `run_gen_step_iter`,说明值需要 yield 给用户
37
+ """
38
+ value: Any
39
+
40
+
41
+ @dataclass(slots=True, frozen=True)
42
+ class YieldFrom:
43
+ """专供 `run_gen_step_iter`,说明值需要解包后逐个 yield 给用户
44
+ """
45
+ value: Any
46
+
47
+
48
+ def iter_gen_step_sync[Y](
49
+ gen: Generator[Y, None | Y, Y],
50
+ /,
51
+ ) -> Generator[Y, Any, Any]:
52
+ send = gen.send
53
+ try:
54
+ v = None
55
+ while True:
56
+ yield (v := send(v))
57
+ except StopIteration as e:
58
+ yield e.value
59
+ finally:
60
+ gen.close()
61
+
62
+
63
+ async def iter_gen_step_async[Y](
64
+ gen: Generator[Awaitable[Y], None | Y, Awaitable[Y] | Y],
65
+ /,
66
+ ) -> AsyncGenerator[Y, Any]:
67
+ send = gen.send
68
+ throw = gen.throw
69
+ try:
70
+ r = send(None)
71
+ while True:
72
+ try:
73
+ yield (v := await r)
74
+ except BaseException as e:
75
+ r = throw(e)
76
+ else:
77
+ r = send(v)
78
+ raise AsyncCancelledError
79
+ except StopIteration as e:
80
+ v = e.value
81
+ if isawaitable(v):
82
+ v = await v
83
+ yield v
84
+ finally:
85
+ gen.close()
86
+
87
+
88
+ @overload
89
+ def iter_gen_step[Y](
90
+ gen: Generator[Y, None | Y, Y] | Callable[[], Generator[Y, None | Y, Y]],
91
+ /,
92
+ async_: Literal[False] = False,
93
+ ) -> Generator[Y, Any, Any]:
94
+ ...
95
+ @overload
96
+ def iter_gen_step[Y](
97
+ gen: Generator[Awaitable[Y], None | Y, Awaitable[Y] | Y] | Callable[[], Generator[Awaitable[Y], None | Y, Awaitable[Y] | Y]],
98
+ /,
99
+ async_: Literal[True],
100
+ ) -> AsyncGenerator[Y, Any]:
101
+ ...
102
+ def iter_gen_step[Y](
103
+ gen: Generator[Y, None | Y, Y] | Callable[[], Generator[Y, None | Y, Y]] | Generator[Awaitable[Y], None | Y, Awaitable[Y] | Y] | Callable[[], Generator[Awaitable[Y], None | Y, Awaitable[Y] | Y]],
104
+ /,
105
+ async_: Literal[False, True] = False,
106
+ ) -> Generator[Y, Any, Any] | AsyncGenerator[Y, Any]:
107
+ if not isinstance(gen, Generator):
108
+ gen = gen()
109
+ if async_:
110
+ gen = cast(Generator[Awaitable[Y], None | Y, Awaitable[Y] | Y], gen)
111
+ return iter_gen_step_async(gen)
112
+ else:
113
+ gen = cast(Generator[Y, None | Y, Y], gen)
114
+ return iter_gen_step_sync(gen)
115
+
116
+
117
+ def run_gen_step_sync[Y, T](
118
+ gen: Generator[Y, None | Y, T],
119
+ /,
120
+ running: SupportsBool = True,
121
+ ) -> T:
122
+ send = gen.send
123
+ try:
124
+ v = None
125
+ while running:
126
+ v = send(v)
127
+ raise CancelledError
128
+ except StopIteration as e:
129
+ return e.value
130
+ finally:
131
+ gen.close()
132
+
133
+
134
+ async def run_gen_step_async[Y, T](
135
+ gen: Generator[Awaitable[Y], None | Y, Awaitable[T] | T],
136
+ /,
137
+ running: SupportsBool = True,
138
+ ) -> T:
139
+ send = gen.send
140
+ throw = gen.throw
141
+ try:
142
+ r = send(None)
143
+ while running:
144
+ try:
145
+ v = await r
146
+ except BaseException as e:
147
+ r = throw(e)
148
+ else:
149
+ r = send(v)
150
+ raise AsyncCancelledError
151
+ except StopIteration as e:
152
+ v = e.value
153
+ if isawaitable(v):
154
+ v = await v
155
+ return v
156
+ finally:
157
+ gen.close()
158
+
159
+
160
+ @overload
161
+ def run_gen_step[T](
162
+ gen: Generator[Any, Any, T] | Callable[[], Generator[Any, Any, T]],
163
+ /,
164
+ async_: Literal[False] = False,
165
+ running: SupportsBool = True,
166
+ ) -> T:
167
+ ...
168
+ @overload
169
+ def run_gen_step[T](
170
+ gen: Generator[Awaitable, Any, Awaitable[T] | T] | Callable[[], Generator[Awaitable, Any, Awaitable[T] | T]],
171
+ /,
172
+ async_: Literal[True],
173
+ running: SupportsBool = True,
174
+ ) -> Awaitable[T]:
175
+ ...
176
+ def run_gen_step(
177
+ gen,
178
+ /,
179
+ async_: Literal[False, True] = False,
180
+ running: SupportsBool = True,
181
+ ):
182
+ if not isinstance(gen, Generator):
183
+ gen = gen()
184
+ if async_:
185
+ return run_gen_step_async(gen, running)
186
+ else:
187
+ return run_gen_step_sync(gen, running)
188
+
189
+
190
+ def run_gen_step_iter_sync(
191
+ gen: Generator,
192
+ /,
193
+ running: SupportsBool = True,
194
+ ) -> Iterator:
195
+ send = gen.send
196
+ try:
197
+ v: Any = None
198
+ while running:
199
+ v = send(v)
200
+ if isinstance(v, Yield):
201
+ v = v.value
202
+ yield v
203
+ elif isinstance(v, YieldFrom):
204
+ v = yield from v.value
205
+ raise CancelledError
206
+ except StopIteration as e:
207
+ return e.value
208
+ finally:
209
+ gen.close()
210
+
211
+
212
+ async def run_gen_step_iter_async(
213
+ gen: Generator,
214
+ /,
215
+ running: SupportsBool = True,
216
+ ) -> AsyncIterator:
217
+ send = gen.send
218
+ throw = gen.throw
219
+ try:
220
+ v = send(None)
221
+ while running:
222
+ try:
223
+ if isinstance(v, Yield):
224
+ yield_type = 1
225
+ v = v.value
226
+ elif isinstance(v, YieldFrom):
227
+ yield_type = 2
228
+ v = v.value
229
+ else:
230
+ yield_type = 0
231
+ if isawaitable(v):
232
+ v = await v
233
+ match yield_type:
234
+ case 1:
235
+ yield v
236
+ case 2:
237
+ if isinstance(v, AsyncIterable):
238
+ async for e in v:
239
+ yield e
240
+ else:
241
+ for e in v:
242
+ yield e
243
+ except BaseException as e:
244
+ v = throw(e)
245
+ else:
246
+ v = send(v)
247
+ except StopIteration as e:
248
+ v = e.value
249
+ if isawaitable(v):
250
+ v = await v
251
+ #return v
252
+ finally:
253
+ gen.close()
254
+
255
+
256
+ @overload
257
+ def run_gen_step_iter(
258
+ gen: Generator | Callable[[], Generator],
259
+ /,
260
+ async_: Literal[False] = False,
261
+ running: SupportsBool = True,
262
+ ) -> Iterator:
263
+ ...
264
+ @overload
265
+ def run_gen_step_iter(
266
+ gen: Generator | Callable[[], Generator],
267
+ /,
268
+ async_: Literal[True],
269
+ running: SupportsBool = True,
270
+ ) -> AsyncIterator:
271
+ ...
272
+ def run_gen_step_iter(
273
+ gen: Generator | Callable[[], Generator],
274
+ /,
275
+ async_: Literal[False, True] = False,
276
+ running: SupportsBool = True,
277
+ ) -> Iterator | AsyncIterator:
278
+ if not isinstance(gen, Generator):
279
+ gen = gen()
280
+ if async_:
281
+ return run_gen_step_iter_async(gen, running)
282
+ else:
283
+ return run_gen_step_iter_sync(gen, running)
284
+
285
+
286
+ @optional
287
+ def as_gen_step(
288
+ func: Callable[..., Generator],
289
+ /,
290
+ async_: None | bool = None,
291
+ is_iter: bool = False,
292
+ ):
293
+ has_async_arg = has_keyword_arg(func, "async_")
294
+ if is_iter:
295
+ run: Callable = run_gen_step_iter
296
+ else:
297
+ run = run_gen_step
298
+ def wrapper(*args, async_: None | bool = async_, **kwds):
299
+ if async_ is None:
300
+ async_ = in_async()
301
+ if has_async_arg:
302
+ kwds["async_"] = async_
303
+ return run(func(*args, **kwds), async_)
304
+ return wrapper
305
+
306
+
307
+ @overload
308
+ def with_iter_next[T](
309
+ iterable: Iterable[T],
310
+ /,
311
+ ) -> ContextManager[Callable[[], T]]:
312
+ ...
313
+ @overload
314
+ def with_iter_next[T](
315
+ iterable: AsyncIterable[T],
316
+ /,
317
+ ) -> ContextManager[Callable[[], Awaitable[T]]]:
318
+ ...
319
+ @contextmanager
320
+ def with_iter_next[T](
321
+ iterable: Iterable[T] | AsyncIterable[T],
322
+ /,
323
+ ):
324
+ """包装迭代器,以供 `run_gen_step` 和 `run_gen_step_iter` 使用
325
+
326
+ .. code:: python
327
+
328
+ if async_:
329
+ async def process():
330
+ async for e in iterable:
331
+ do_what_you_want(e)
332
+ return process()
333
+ else:
334
+ for e in iterable:
335
+ do_what_you_want(e)
336
+
337
+ 大概相当于
338
+
339
+ .. code:: python
340
+
341
+ def gen_step():
342
+ with with_iter_next(iterable) as do_next:
343
+ while True:
344
+ e = yield do_next()
345
+ do_what_you_want(e)
346
+
347
+ run_gen_step(gen_step, async_)
348
+ """
349
+ if isinstance(iterable, AsyncIterable):
350
+ try:
351
+ yield aiter(iterable).__anext__
352
+ except StopAsyncIteration:
353
+ pass
354
+ else:
355
+ try:
356
+ yield iter(iterable).__next__
357
+ except StopIteration:
358
+ pass
359
+
360
+
361
+ @overload
362
+ def split_cm[T](
363
+ cm: AbstractContextManager[T],
364
+ /,
365
+ ) -> tuple[Callable[[], T], Callable[[], Any]]:
366
+ ...
367
+ @overload
368
+ def split_cm[T](
369
+ cm: AbstractAsyncContextManager[T],
370
+ /,
371
+ ) -> tuple[Callable[[], Coroutine[Any, Any, T]], Callable[[], Coroutine]]:
372
+ ...
373
+ def split_cm[T](
374
+ cm: AbstractContextManager[T] | AbstractAsyncContextManager[T],
375
+ /,
376
+ ) -> (
377
+ tuple[Callable[[], T], Callable[[], Any]] |
378
+ tuple[Callable[[], Coroutine[Any, Any, T]], Callable[[], Coroutine]]
379
+ ):
380
+ """拆分上下文管理器,以供 `run_gen_step` 和 `run_gen_step_iter` 使用
381
+
382
+ .. code:: python
383
+
384
+ if async_:
385
+ async def process():
386
+ async with cm as obj:
387
+ do_what_you_want(obj)
388
+ return process()
389
+ else:
390
+ with cm as obj:
391
+ do_what_you_want(obj)
392
+
393
+ 大概相当于
394
+
395
+ .. code:: python
396
+
397
+ def gen_step():
398
+ enter, exit = split_cm(cm)
399
+ obj = yield enter()
400
+ try:
401
+ do_what_you_want(obj)
402
+ finally:
403
+ yield exit()
404
+
405
+ run_gen_step(gen_step, async_)
406
+ """
407
+ if isinstance(cm, AbstractAsyncContextManager):
408
+ enter: Callable = cm.__aenter__
409
+ exit: Callable = cm.__aexit__
410
+ else:
411
+ enter = cm.__enter__
412
+ exit = cm.__exit__
413
+ return enter, lambda: exit(*exc_info())
414
+
@@ -1,38 +1,31 @@
1
1
  #!/usr/bin/env python3
2
2
  # encoding: utf-8
3
3
 
4
- __author__ = "ChenyangGao <https://chenyanggao.github.io>"
5
- __version__ = (0, 2, 10)
6
4
  __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",
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",
14
9
  "async_iter_unique", "wrap_iter", "wrap_aiter", "peek_iter", "peek_aiter",
15
- "acc_step", "cut_iter", "bfs_gen", "context", "backgroud_loop",
10
+ "acc_step", "cut_iter", "context", "backgroud_loop", "gen_startup",
11
+ "async_gen_startup", "do_iter", "do_aiter", "bfs_iter", "bfs_gen",
16
12
  ]
17
13
 
18
14
  from asyncio import create_task, sleep as async_sleep
19
15
  from builtins import map as _map, filter as _filter, zip as _zip
20
16
  from collections import defaultdict, deque
21
17
  from collections.abc import (
22
- AsyncIterable, AsyncIterator, Awaitable, Buffer, Callable,
23
- Collection, Container, Coroutine, Generator, Iterable, Iterator,
24
- Mapping, MutableMapping, MutableSet, MutableSequence, Sequence,
25
- ValuesView,
26
- )
27
- from contextlib import (
28
- asynccontextmanager, contextmanager, ExitStack, AsyncExitStack,
29
- AbstractContextManager, AbstractAsyncContextManager,
18
+ AsyncGenerator, AsyncIterable, AsyncIterator, Awaitable, Buffer,
19
+ Callable, Collection, Container, Coroutine, Generator, Iterable,
20
+ Iterator, Mapping, MutableMapping, MutableSet, MutableSequence,
21
+ Sequence, ValuesView,
30
22
  )
23
+ from contextlib import asynccontextmanager, contextmanager, ExitStack, AsyncExitStack
31
24
  from copy import copy
32
- from dataclasses import dataclass
25
+ from functools import update_wrapper
33
26
  from itertools import batched, chain as _chain, pairwise
34
- from inspect import iscoroutinefunction, signature
35
- from sys import _getframe, exc_info
27
+ from inspect import iscoroutinefunction
28
+ from sys import _getframe
36
29
  from _thread import start_new_thread
37
30
  from time import sleep, time
38
31
  from types import FrameType
@@ -48,36 +41,6 @@ from texttools import format_time
48
41
  from undefined import undefined
49
42
 
50
43
 
51
- @dataclass(slots=True, frozen=True, unsafe_hash=True)
52
- class Yield:
53
- """专供 `run_gen_step_iter`,说明值需要 yield 给用户
54
- """
55
- value: Any
56
-
57
-
58
- @dataclass(slots=True, frozen=True, unsafe_hash=True)
59
- class YieldFrom:
60
- """专供 `run_gen_step_iter`,说明值需要解包后逐个 yield 给用户
61
- """
62
- value: Any
63
-
64
-
65
- @dataclass(slots=True, frozen=True, unsafe_hash=True)
66
- class GenStep:
67
- """专供 `run_gen_step` 和 `run_gen_step_iter`,说明值需要进一步提取
68
- """
69
- value: Generator
70
- max_depth: int = 0
71
-
72
-
73
- @dataclass(slots=True, frozen=True, unsafe_hash=True)
74
- class GenStepIter:
75
- """专供 `run_gen_step_iter`,说明值需要进一步提取
76
- """
77
- value: Generator
78
- max_depth: int = 0
79
-
80
-
81
44
  iterable = Iterable.__instancecheck__
82
45
  async_iterable = AsyncIterable.__instancecheck__
83
46
  isawaitable = Awaitable.__instancecheck__
@@ -106,406 +69,6 @@ def _get_async[T](back: int = 2, /, *, default: Literal[False] | T = False) -> b
106
69
  return _coalesce(iter_frams(), default)
107
70
 
108
71
 
109
- def _run_gen_step(
110
- gen: Generator,
111
- /,
112
- drive_generator: int = 0,
113
- ):
114
- send = gen.send
115
- throw = gen.throw
116
- dgen = drive_generator if drive_generator < 0 else drive_generator - 1
117
- try:
118
- value: Any = send(None)
119
- while True:
120
- try:
121
- if isinstance(value, GenStep):
122
- value = _run_gen_step(value.value, value.max_depth)
123
- elif drive_generator and isinstance(value, Generator):
124
- value = _run_gen_step(value, dgen)
125
- except BaseException as e:
126
- value = throw(e)
127
- else:
128
- value = send(value)
129
- except StopIteration as e:
130
- value = e.value
131
- if isinstance(value, GenStep):
132
- value = _run_gen_step(value.value, value.max_depth)
133
- elif drive_generator and isinstance(value, Generator):
134
- value = _run_gen_step(value, dgen)
135
- return value
136
- finally:
137
- gen.close()
138
-
139
-
140
- async def _run_gen_step_async(
141
- gen: Generator,
142
- /,
143
- drive_generator: int = 0,
144
- ):
145
- send = gen.send
146
- throw = gen.throw
147
- dgen = drive_generator if drive_generator < 0 else drive_generator - 1
148
- try:
149
- value: Any = send(None)
150
- while True:
151
- try:
152
- if isawaitable(value):
153
- value = await value
154
- elif isinstance(value, GenStep):
155
- value = await _run_gen_step_async(value.value, value.max_depth)
156
- elif drive_generator and isinstance(value, Generator):
157
- value = await _run_gen_step_async(value, dgen)
158
- except BaseException as e:
159
- value = throw(e)
160
- else:
161
- value = send(value)
162
- except StopIteration as e:
163
- value = e.value
164
- if isawaitable(value):
165
- value = await value
166
- elif isinstance(value, GenStep):
167
- value = await _run_gen_step_async(value.value, value.max_depth)
168
- elif drive_generator and isinstance(value, Generator):
169
- value = await _run_gen_step_async(value, dgen)
170
- return value
171
- finally:
172
- gen.close()
173
-
174
-
175
- def run_gen_step[**Args](
176
- gen_step: Generator | Callable[Args, Generator],
177
- async_: None | Literal[False, True] = None,
178
- /,
179
- *args: Args.args,
180
- **kwds: Args.kwargs,
181
- ):
182
- """驱动生成器运行,并返回其结果
183
- """
184
- if async_ is None:
185
- async_ = _get_async()
186
- if not isinstance(gen_step, Generator):
187
- params = signature(gen_step).parameters
188
- if ((param := params.get("async_")) and
189
- (param.kind in (param.POSITIONAL_OR_KEYWORD, param.KEYWORD_ONLY))
190
- ):
191
- kwds.setdefault("async_", async_)
192
- gen_step = gen_step(*args, **kwds)
193
- if async_:
194
- return _run_gen_step_async(gen_step)
195
- else:
196
- return _run_gen_step(gen_step)
197
-
198
-
199
- def _run_gen_step_iter(
200
- gen: Generator,
201
- /,
202
- drive_generator: int = 0,
203
- ) -> Iterator:
204
- send = gen.send
205
- throw = gen.throw
206
- dgen = drive_generator if drive_generator < 0 else drive_generator - 1
207
- try:
208
- value: Any = send(None)
209
- while True:
210
- try:
211
- val_type = type(value)
212
- if issubclass(val_type, (Yield, YieldFrom)):
213
- value = value.value
214
- if isinstance(value, GenStepIter):
215
- yield from _run_gen_step_iter(value.value, value.max_depth)
216
- else:
217
- if isinstance(value, GenStep):
218
- value = _run_gen_step(value.value, value.max_depth)
219
- elif drive_generator and isinstance(value, Generator):
220
- value = _run_gen_step(value, dgen)
221
- if val_type is Yield:
222
- yield value
223
- elif val_type is YieldFrom:
224
- yield from value
225
- except BaseException as e:
226
- value = throw(e)
227
- else:
228
- value = send(value)
229
- except StopIteration as e:
230
- value = e.value
231
- val_type = type(value)
232
- if issubclass(val_type, (Yield, YieldFrom)):
233
- value = value.value
234
- elif isinstance(value, GenStepIter):
235
- yield from _run_gen_step_iter(value.value, value.max_depth)
236
- elif isinstance(value, GenStep):
237
- value = _run_gen_step(value.value, value.max_depth)
238
- elif drive_generator and isinstance(value, Generator):
239
- value = _run_gen_step(value, dgen)
240
- if val_type is Yield:
241
- yield value
242
- elif val_type is YieldFrom:
243
- yield from value
244
- finally:
245
- gen.close()
246
-
247
-
248
- async def _run_gen_step_async_iter(
249
- gen: Generator,
250
- /,
251
- drive_generator: int = 0,
252
- ) -> AsyncIterator:
253
- send = gen.send
254
- throw = gen.throw
255
- dgen = drive_generator if drive_generator < 0 else drive_generator - 1
256
- try:
257
- value: Any = send(None)
258
- while True:
259
- try:
260
- val_type = type(value)
261
- if issubclass(val_type, (Yield, YieldFrom)):
262
- value = value.value
263
- if isawaitable(value):
264
- value = await value
265
- elif isinstance(value, GenStepIter):
266
- async for el in _run_gen_step_async_iter(value.value, value.max_depth):
267
- yield el
268
- elif isinstance(value, GenStep):
269
- value = await _run_gen_step_async(value.value, value.max_depth)
270
- elif drive_generator and isinstance(value, Generator):
271
- value = await _run_gen_step_async(value, dgen)
272
- if val_type is Yield:
273
- yield value
274
- elif val_type is YieldFrom:
275
- if isinstance(value, AsyncIterable):
276
- async for el in value:
277
- yield el
278
- else:
279
- for el in value:
280
- yield el
281
- except BaseException as e:
282
- value = throw(e)
283
- else:
284
- value = send(value)
285
- except StopIteration as e:
286
- value = e.value
287
- val_type = type(value)
288
- if issubclass(val_type, (Yield, YieldFrom)):
289
- value = value.value
290
- if isawaitable(value):
291
- value = await value
292
- if isinstance(value, GenStepIter):
293
- async for el in _run_gen_step_async_iter(value.value, value.max_depth):
294
- yield el
295
- else:
296
- if isinstance(value, GenStep):
297
- value = await _run_gen_step_async(value.value, value.max_depth)
298
- elif drive_generator and isinstance(value, Generator):
299
- value = await _run_gen_step_async(value, dgen)
300
- if val_type is Yield:
301
- yield value
302
- elif val_type is YieldFrom:
303
- if isinstance(value, AsyncIterable):
304
- async for el in value:
305
- yield el
306
- else:
307
- for el in value:
308
- yield el
309
- finally:
310
- gen.close()
311
-
312
-
313
- @overload
314
- def run_gen_step_iter[**Args](
315
- gen_step: Generator | Callable[Args, Generator],
316
- async_: None = None,
317
- /,
318
- *args: Args.args,
319
- **kwds: Args.kwargs,
320
- ) -> Iterator | AsyncIterator:
321
- ...
322
- @overload
323
- def run_gen_step_iter[**Args](
324
- gen_step: Generator | Callable[Args, Generator],
325
- async_: Literal[False] = False,
326
- /,
327
- *args: Args.args,
328
- **kwds: Args.kwargs,
329
- ) -> Iterator:
330
- ...
331
- @overload
332
- def run_gen_step_iter[**Args](
333
- gen_step: Generator | Callable[Args, Generator],
334
- async_: Literal[True],
335
- /,
336
- *args: Args.args,
337
- **kwds: Args.kwargs,
338
- ) -> AsyncIterator:
339
- ...
340
- def run_gen_step_iter[**Args](
341
- gen_step: Generator | Callable[Args, Generator],
342
- async_: None | Literal[False, True] = None,
343
- /,
344
- *args: Args.args,
345
- **kwds: Args.kwargs,
346
- ) -> Iterator | AsyncIterator:
347
- """驱动生成器运行,并从中返回可迭代而出的值
348
- """
349
- if async_ is None:
350
- async_ = _get_async()
351
- if not isinstance(gen_step, Generator):
352
- params = signature(gen_step).parameters
353
- if ((param := params.get("async_")) and
354
- (param.kind in (param.POSITIONAL_OR_KEYWORD, param.KEYWORD_ONLY))
355
- ):
356
- kwds.setdefault("async_", async_)
357
- gen_step = gen_step(*args, **kwds)
358
- if async_:
359
- return _run_gen_step_async_iter(gen_step)
360
- else:
361
- return _run_gen_step_iter(gen_step)
362
-
363
-
364
- def as_gen_step[**Args](
365
- gen_step: Callable[Args, Generator],
366
- /,
367
- ) -> Callable[Args, Any]:
368
- default_async = _get_async(default=None)
369
- def wrapper(*args: Args.args, **kwds: Args.kwargs):
370
- return run_gen_step(
371
- gen_step,
372
- _coalesce((
373
- kwds.pop("async_", None),
374
- _get_async(default=default_async),
375
- )),
376
- *args,
377
- **kwds,
378
- )
379
- return wrapper
380
-
381
-
382
- def as_gen_step_iter[**Args](
383
- gen_step: Callable[Args, Generator],
384
- /,
385
- ) -> Callable[Args, Iterable | AsyncIterable]:
386
- default_async = _get_async(default=None)
387
- def wrapper(*args: Args.args, **kwds: Args.kwargs):
388
- return run_gen_step_iter(
389
- gen_step,
390
- _coalesce((
391
- kwds.pop("async_", None),
392
- _get_async(default=default_async),
393
- )),
394
- *args,
395
- **kwds,
396
- )
397
- return wrapper
398
-
399
-
400
- @overload
401
- def split_cm[T](
402
- cm: AbstractContextManager[T],
403
- /,
404
- ) -> tuple[Callable[[], T], Callable[[], Any]]:
405
- ...
406
- @overload
407
- def split_cm[T](
408
- cm: AbstractAsyncContextManager[T],
409
- /,
410
- ) -> tuple[Callable[[], Coroutine[Any, Any, T]], Callable[[], Coroutine]]:
411
- ...
412
- def split_cm[T](
413
- cm: AbstractContextManager[T] | AbstractAsyncContextManager[T],
414
- /,
415
- ) -> (
416
- tuple[Callable[[], T], Callable[[], Any]] |
417
- tuple[Callable[[], Coroutine[Any, Any, T]], Callable[[], Coroutine]]
418
- ):
419
- """拆分上下文管理器,以供 `run_gen_step` 和 `run_gen_step_iter` 使用
420
-
421
- .. code:: python
422
-
423
- if async_:
424
- async def process():
425
- async with cm as obj:
426
- do_what_you_want()
427
- return process()
428
- else:
429
- with cm as obj:
430
- do_what_you_want()
431
-
432
- 大概相当于
433
-
434
- .. code:: python
435
-
436
- def gen_step():
437
- enter, exit = split_cm(cm)
438
- obj = yield enter()
439
- try:
440
- do_what_you_want()
441
- finally:
442
- yield exit()
443
-
444
- run_gen_step(gen_step, async_)
445
- """
446
- if isinstance(cm, AbstractAsyncContextManager):
447
- enter: Callable = cm.__aenter__
448
- exit: Callable = cm.__aexit__
449
- else:
450
- enter = cm.__enter__
451
- exit = cm.__exit__
452
- return enter, lambda: exit(*exc_info())
453
-
454
-
455
- @overload
456
- def with_iter_next[T](
457
- iterable: Iterable[T],
458
- /,
459
- ) -> ContextManager[Callable[[], T]]:
460
- ...
461
- @overload
462
- def with_iter_next[T](
463
- iterable: AsyncIterable[T],
464
- /,
465
- ) -> ContextManager[Callable[[], Awaitable[T]]]:
466
- ...
467
- @contextmanager
468
- def with_iter_next[T](
469
- iterable: Iterable[T] | AsyncIterable[T],
470
- /,
471
- ):
472
- """包装迭代器,以供 `run_gen_step` 和 `run_gen_step_iter` 使用
473
-
474
- .. code:: python
475
-
476
- if async_:
477
- async def process():
478
- async for e in iterable:
479
- do_what_you_want()
480
- return process()
481
- else:
482
- for e in iterable:
483
- do_what_you_want()
484
-
485
- 大概相当于
486
-
487
- .. code:: python
488
-
489
- def gen_step():
490
- with with_iter_next(iterable) as do_next:
491
- while True:
492
- e = yield do_next()
493
- do_what_you_want()
494
-
495
- run_gen_step(gen_step, async_)
496
- """
497
- if isinstance(iterable, AsyncIterable):
498
- try:
499
- yield aiter(iterable).__anext__
500
- except StopAsyncIteration:
501
- pass
502
- else:
503
- try:
504
- yield iter(iterable).__next__
505
- except StopIteration:
506
- pass
507
-
508
-
509
72
  def map(
510
73
  function: None | Callable,
511
74
  iterable: Iterable | AsyncIterable,
@@ -631,14 +194,40 @@ def chain_from_iterable[T](
631
194
  ...
632
195
  @overload
633
196
  def chain_from_iterable[T](
634
- iterables: Iterable[Iterable[T] | AsyncIterable[T]] | AsyncIterable[Iterable[T] | AsyncIterable[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
+ ),
635
217
  threaded: bool = False,
636
218
  *,
637
219
  async_: Literal[True],
638
220
  ) -> AsyncIterator[T]:
639
221
  ...
640
222
  def chain_from_iterable[T](
641
- iterables: Iterable[Iterable[T]] | Iterable[Iterable[T] | AsyncIterable[T]] | AsyncIterable[Iterable[T] | AsyncIterable[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
+ ),
642
231
  threaded: bool = False,
643
232
  *,
644
233
  async_: Literal[False, True] = False,
@@ -1264,48 +853,6 @@ def cut_iter(
1264
853
  yield stop, stop - start
1265
854
 
1266
855
 
1267
- @overload
1268
- def bfs_gen[T](
1269
- initial: T,
1270
- /,
1271
- unpack_iterator: Literal[False] = False,
1272
- ) -> Generator[T, T | None, None]:
1273
- ...
1274
- @overload
1275
- def bfs_gen[T](
1276
- initial: T | Iterator[T],
1277
- /,
1278
- unpack_iterator: Literal[True],
1279
- ) -> Generator[T, T | None, None]:
1280
- ...
1281
- def bfs_gen[T](
1282
- initial: T | Iterator[T],
1283
- /,
1284
- unpack_iterator: bool = False,
1285
- ) -> Generator[T, T | None, None]:
1286
- """辅助函数,返回生成器,用来简化广度优先遍历
1287
- """
1288
- dq: deque[T] = deque()
1289
- push, pushmany, pop = dq.append, dq.extend, dq.popleft
1290
- if isinstance(initial, Iterator) and unpack_iterator:
1291
- pushmany(initial)
1292
- else:
1293
- push(initial) # type: ignore
1294
- while dq:
1295
- args: None | T = yield (val := pop())
1296
- if unpack_iterator:
1297
- while args is not None:
1298
- if isinstance(args, Iterator):
1299
- pushmany(args)
1300
- else:
1301
- push(args)
1302
- args = yield val
1303
- else:
1304
- while args is not None:
1305
- push(args)
1306
- args = yield val
1307
-
1308
-
1309
856
  @overload
1310
857
  def context[T](
1311
858
  func: Callable[..., T],
@@ -1445,3 +992,71 @@ def backgroud_loop(
1445
992
  print("\r\x1b[K", end="")
1446
993
  return ctx()
1447
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: 这个模块添加了很多不必要的函数,需要进行移除
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "python-iterutils"
3
- version = "0.2.10.1"
3
+ version = "0.3.0"
4
4
  description = "Python another itertools."
5
5
  authors = ["ChenyangGao <wosiwujm@gmail.com>"]
6
6
  license = "MIT"
@@ -27,9 +27,9 @@ include = [
27
27
 
28
28
  [tool.poetry.dependencies]
29
29
  python = "^3.12"
30
- python-asynctools = ">=0.1.3.4"
31
- python-texttools = ">=0.0.3"
32
- python-undefined = ">=0.0.3"
30
+ python-asynctools = ">=0.1.4"
31
+ python-texttools = ">=0.0.6"
32
+ python-undefined = ">=0.0.4"
33
33
 
34
34
  [build-system]
35
35
  requires = ["poetry-core"]