python-iterutils 0.2.10.2__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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-iterutils
3
- Version: 0.2.10.2
3
+ Version: 0.3.0
4
4
  Summary: Python another itertools.
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -21,9 +21,9 @@ Classifier: Programming Language :: Python :: 3 :: Only
21
21
  Classifier: Topic :: Software Development
22
22
  Classifier: Topic :: Software Development :: Libraries
23
23
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
- Requires-Dist: python-asynctools (>=0.1.3.4)
25
- Requires-Dist: python-texttools (>=0.0.3)
26
- 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
27
  Project-URL: Homepage, https://github.com/ChenyangGao/python-modules/tree/main/python-iterutils
28
28
  Project-URL: Repository, https://github.com/ChenyangGao/python-modules/tree/main/python-iterutils
29
29
  Description-Content-Type: text/markdown
@@ -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,16 +1,11 @@
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
10
  "acc_step", "cut_iter", "context", "backgroud_loop", "gen_startup",
16
11
  "async_gen_startup", "do_iter", "do_aiter", "bfs_iter", "bfs_gen",
@@ -25,16 +20,12 @@ from collections.abc import (
25
20
  Iterator, Mapping, MutableMapping, MutableSet, MutableSequence,
26
21
  Sequence, ValuesView,
27
22
  )
28
- from contextlib import (
29
- asynccontextmanager, contextmanager, ExitStack, AsyncExitStack,
30
- AbstractContextManager, AbstractAsyncContextManager,
31
- )
23
+ from contextlib import asynccontextmanager, contextmanager, ExitStack, AsyncExitStack
32
24
  from copy import copy
33
- from dataclasses import dataclass
34
25
  from functools import update_wrapper
35
26
  from itertools import batched, chain as _chain, pairwise
36
- from inspect import iscoroutinefunction, signature
37
- from sys import _getframe, exc_info
27
+ from inspect import iscoroutinefunction
28
+ from sys import _getframe
38
29
  from _thread import start_new_thread
39
30
  from time import sleep, time
40
31
  from types import FrameType
@@ -50,36 +41,6 @@ from texttools import format_time
50
41
  from undefined import undefined
51
42
 
52
43
 
53
- @dataclass(slots=True, frozen=True, unsafe_hash=True)
54
- class Yield:
55
- """专供 `run_gen_step_iter`,说明值需要 yield 给用户
56
- """
57
- value: Any
58
-
59
-
60
- @dataclass(slots=True, frozen=True, unsafe_hash=True)
61
- class YieldFrom:
62
- """专供 `run_gen_step_iter`,说明值需要解包后逐个 yield 给用户
63
- """
64
- value: Any
65
-
66
-
67
- @dataclass(slots=True, frozen=True, unsafe_hash=True)
68
- class GenStep:
69
- """专供 `run_gen_step` 和 `run_gen_step_iter`,说明值需要进一步提取
70
- """
71
- value: Generator
72
- max_depth: int = 0
73
-
74
-
75
- @dataclass(slots=True, frozen=True, unsafe_hash=True)
76
- class GenStepIter:
77
- """专供 `run_gen_step_iter`,说明值需要进一步提取
78
- """
79
- value: Generator
80
- max_depth: int = 0
81
-
82
-
83
44
  iterable = Iterable.__instancecheck__
84
45
  async_iterable = AsyncIterable.__instancecheck__
85
46
  isawaitable = Awaitable.__instancecheck__
@@ -108,406 +69,6 @@ def _get_async[T](back: int = 2, /, *, default: Literal[False] | T = False) -> b
108
69
  return _coalesce(iter_frams(), default)
109
70
 
110
71
 
111
- def _run_gen_step(
112
- gen: Generator,
113
- /,
114
- drive_generator: int = 0,
115
- ):
116
- send = gen.send
117
- throw = gen.throw
118
- dgen = drive_generator if drive_generator < 0 else drive_generator - 1
119
- try:
120
- value: Any = send(None)
121
- while True:
122
- try:
123
- if isinstance(value, GenStep):
124
- value = _run_gen_step(value.value, value.max_depth)
125
- elif drive_generator and isinstance(value, Generator):
126
- value = _run_gen_step(value, dgen)
127
- except BaseException as e:
128
- value = throw(e)
129
- else:
130
- value = send(value)
131
- except StopIteration as e:
132
- value = e.value
133
- if isinstance(value, GenStep):
134
- value = _run_gen_step(value.value, value.max_depth)
135
- elif drive_generator and isinstance(value, Generator):
136
- value = _run_gen_step(value, dgen)
137
- return value
138
- finally:
139
- gen.close()
140
-
141
-
142
- async def _run_gen_step_async(
143
- gen: Generator,
144
- /,
145
- drive_generator: int = 0,
146
- ):
147
- send = gen.send
148
- throw = gen.throw
149
- dgen = drive_generator if drive_generator < 0 else drive_generator - 1
150
- try:
151
- value: Any = send(None)
152
- while True:
153
- try:
154
- if isawaitable(value):
155
- value = await value
156
- elif isinstance(value, GenStep):
157
- value = await _run_gen_step_async(value.value, value.max_depth)
158
- elif drive_generator and isinstance(value, Generator):
159
- value = await _run_gen_step_async(value, dgen)
160
- except BaseException as e:
161
- value = throw(e)
162
- else:
163
- value = send(value)
164
- except StopIteration as e:
165
- value = e.value
166
- if isawaitable(value):
167
- value = await value
168
- elif isinstance(value, GenStep):
169
- value = await _run_gen_step_async(value.value, value.max_depth)
170
- elif drive_generator and isinstance(value, Generator):
171
- value = await _run_gen_step_async(value, dgen)
172
- return value
173
- finally:
174
- gen.close()
175
-
176
-
177
- def run_gen_step[**Args](
178
- gen_step: Generator | Callable[Args, Generator],
179
- async_: None | Literal[False, True] = None,
180
- /,
181
- *args: Args.args,
182
- **kwds: Args.kwargs,
183
- ):
184
- """驱动生成器运行,并返回其结果
185
- """
186
- if async_ is None:
187
- async_ = _get_async()
188
- if not isinstance(gen_step, Generator):
189
- params = signature(gen_step).parameters
190
- if ((param := params.get("async_")) and
191
- (param.kind in (param.POSITIONAL_OR_KEYWORD, param.KEYWORD_ONLY))
192
- ):
193
- kwds.setdefault("async_", async_)
194
- gen_step = gen_step(*args, **kwds)
195
- if async_:
196
- return _run_gen_step_async(gen_step)
197
- else:
198
- return _run_gen_step(gen_step)
199
-
200
-
201
- def _run_gen_step_iter(
202
- gen: Generator,
203
- /,
204
- drive_generator: int = 0,
205
- ) -> Iterator:
206
- send = gen.send
207
- throw = gen.throw
208
- dgen = drive_generator if drive_generator < 0 else drive_generator - 1
209
- try:
210
- value: Any = send(None)
211
- while True:
212
- try:
213
- val_type = type(value)
214
- if issubclass(val_type, (Yield, YieldFrom)):
215
- value = value.value
216
- if isinstance(value, GenStepIter):
217
- yield from _run_gen_step_iter(value.value, value.max_depth)
218
- else:
219
- if isinstance(value, GenStep):
220
- value = _run_gen_step(value.value, value.max_depth)
221
- elif drive_generator and isinstance(value, Generator):
222
- value = _run_gen_step(value, dgen)
223
- if val_type is Yield:
224
- yield value
225
- elif val_type is YieldFrom:
226
- yield from value
227
- except BaseException as e:
228
- value = throw(e)
229
- else:
230
- value = send(value)
231
- except StopIteration as e:
232
- value = e.value
233
- val_type = type(value)
234
- if issubclass(val_type, (Yield, YieldFrom)):
235
- value = value.value
236
- elif isinstance(value, GenStepIter):
237
- yield from _run_gen_step_iter(value.value, value.max_depth)
238
- elif isinstance(value, GenStep):
239
- value = _run_gen_step(value.value, value.max_depth)
240
- elif drive_generator and isinstance(value, Generator):
241
- value = _run_gen_step(value, dgen)
242
- if val_type is Yield:
243
- yield value
244
- elif val_type is YieldFrom:
245
- yield from value
246
- finally:
247
- gen.close()
248
-
249
-
250
- async def _run_gen_step_async_iter(
251
- gen: Generator,
252
- /,
253
- drive_generator: int = 0,
254
- ) -> AsyncIterator:
255
- send = gen.send
256
- throw = gen.throw
257
- dgen = drive_generator if drive_generator < 0 else drive_generator - 1
258
- try:
259
- value: Any = send(None)
260
- while True:
261
- try:
262
- val_type = type(value)
263
- if issubclass(val_type, (Yield, YieldFrom)):
264
- value = value.value
265
- if isawaitable(value):
266
- value = await value
267
- elif isinstance(value, GenStepIter):
268
- async for el in _run_gen_step_async_iter(value.value, value.max_depth):
269
- yield el
270
- elif isinstance(value, GenStep):
271
- value = await _run_gen_step_async(value.value, value.max_depth)
272
- elif drive_generator and isinstance(value, Generator):
273
- value = await _run_gen_step_async(value, dgen)
274
- if val_type is Yield:
275
- yield value
276
- elif val_type is YieldFrom:
277
- if isinstance(value, AsyncIterable):
278
- async for el in value:
279
- yield el
280
- else:
281
- for el in value:
282
- yield el
283
- except BaseException as e:
284
- value = throw(e)
285
- else:
286
- value = send(value)
287
- except StopIteration as e:
288
- value = e.value
289
- val_type = type(value)
290
- if issubclass(val_type, (Yield, YieldFrom)):
291
- value = value.value
292
- if isawaitable(value):
293
- value = await value
294
- if isinstance(value, GenStepIter):
295
- async for el in _run_gen_step_async_iter(value.value, value.max_depth):
296
- yield el
297
- else:
298
- if isinstance(value, GenStep):
299
- value = await _run_gen_step_async(value.value, value.max_depth)
300
- elif drive_generator and isinstance(value, Generator):
301
- value = await _run_gen_step_async(value, dgen)
302
- if val_type is Yield:
303
- yield value
304
- elif val_type is YieldFrom:
305
- if isinstance(value, AsyncIterable):
306
- async for el in value:
307
- yield el
308
- else:
309
- for el in value:
310
- yield el
311
- finally:
312
- gen.close()
313
-
314
-
315
- @overload
316
- def run_gen_step_iter[**Args](
317
- gen_step: Generator | Callable[Args, Generator],
318
- async_: None = None,
319
- /,
320
- *args: Args.args,
321
- **kwds: Args.kwargs,
322
- ) -> Iterator | AsyncIterator:
323
- ...
324
- @overload
325
- def run_gen_step_iter[**Args](
326
- gen_step: Generator | Callable[Args, Generator],
327
- async_: Literal[False] = False,
328
- /,
329
- *args: Args.args,
330
- **kwds: Args.kwargs,
331
- ) -> Iterator:
332
- ...
333
- @overload
334
- def run_gen_step_iter[**Args](
335
- gen_step: Generator | Callable[Args, Generator],
336
- async_: Literal[True],
337
- /,
338
- *args: Args.args,
339
- **kwds: Args.kwargs,
340
- ) -> AsyncIterator:
341
- ...
342
- def run_gen_step_iter[**Args](
343
- gen_step: Generator | Callable[Args, Generator],
344
- async_: None | Literal[False, True] = None,
345
- /,
346
- *args: Args.args,
347
- **kwds: Args.kwargs,
348
- ) -> Iterator | AsyncIterator:
349
- """驱动生成器运行,并从中返回可迭代而出的值
350
- """
351
- if async_ is None:
352
- async_ = _get_async()
353
- if not isinstance(gen_step, Generator):
354
- params = signature(gen_step).parameters
355
- if ((param := params.get("async_")) and
356
- (param.kind in (param.POSITIONAL_OR_KEYWORD, param.KEYWORD_ONLY))
357
- ):
358
- kwds.setdefault("async_", async_)
359
- gen_step = gen_step(*args, **kwds)
360
- if async_:
361
- return _run_gen_step_async_iter(gen_step)
362
- else:
363
- return _run_gen_step_iter(gen_step)
364
-
365
-
366
- def as_gen_step[**Args](
367
- gen_step: Callable[Args, Generator],
368
- /,
369
- ) -> Callable[Args, Any]:
370
- default_async = _get_async(default=None)
371
- def wrapper(*args: Args.args, **kwds: Args.kwargs):
372
- return run_gen_step(
373
- gen_step,
374
- _coalesce((
375
- kwds.pop("async_", None),
376
- _get_async(default=default_async),
377
- )),
378
- *args,
379
- **kwds,
380
- )
381
- return wrapper
382
-
383
-
384
- def as_gen_step_iter[**Args](
385
- gen_step: Callable[Args, Generator],
386
- /,
387
- ) -> Callable[Args, Iterable | AsyncIterable]:
388
- default_async = _get_async(default=None)
389
- def wrapper(*args: Args.args, **kwds: Args.kwargs):
390
- return run_gen_step_iter(
391
- gen_step,
392
- _coalesce((
393
- kwds.pop("async_", None),
394
- _get_async(default=default_async),
395
- )),
396
- *args,
397
- **kwds,
398
- )
399
- return wrapper
400
-
401
-
402
- @overload
403
- def split_cm[T](
404
- cm: AbstractContextManager[T],
405
- /,
406
- ) -> tuple[Callable[[], T], Callable[[], Any]]:
407
- ...
408
- @overload
409
- def split_cm[T](
410
- cm: AbstractAsyncContextManager[T],
411
- /,
412
- ) -> tuple[Callable[[], Coroutine[Any, Any, T]], Callable[[], Coroutine]]:
413
- ...
414
- def split_cm[T](
415
- cm: AbstractContextManager[T] | AbstractAsyncContextManager[T],
416
- /,
417
- ) -> (
418
- tuple[Callable[[], T], Callable[[], Any]] |
419
- tuple[Callable[[], Coroutine[Any, Any, T]], Callable[[], Coroutine]]
420
- ):
421
- """拆分上下文管理器,以供 `run_gen_step` 和 `run_gen_step_iter` 使用
422
-
423
- .. code:: python
424
-
425
- if async_:
426
- async def process():
427
- async with cm as obj:
428
- do_what_you_want()
429
- return process()
430
- else:
431
- with cm as obj:
432
- do_what_you_want()
433
-
434
- 大概相当于
435
-
436
- .. code:: python
437
-
438
- def gen_step():
439
- enter, exit = split_cm(cm)
440
- obj = yield enter()
441
- try:
442
- do_what_you_want()
443
- finally:
444
- yield exit()
445
-
446
- run_gen_step(gen_step, async_)
447
- """
448
- if isinstance(cm, AbstractAsyncContextManager):
449
- enter: Callable = cm.__aenter__
450
- exit: Callable = cm.__aexit__
451
- else:
452
- enter = cm.__enter__
453
- exit = cm.__exit__
454
- return enter, lambda: exit(*exc_info())
455
-
456
-
457
- @overload
458
- def with_iter_next[T](
459
- iterable: Iterable[T],
460
- /,
461
- ) -> ContextManager[Callable[[], T]]:
462
- ...
463
- @overload
464
- def with_iter_next[T](
465
- iterable: AsyncIterable[T],
466
- /,
467
- ) -> ContextManager[Callable[[], Awaitable[T]]]:
468
- ...
469
- @contextmanager
470
- def with_iter_next[T](
471
- iterable: Iterable[T] | AsyncIterable[T],
472
- /,
473
- ):
474
- """包装迭代器,以供 `run_gen_step` 和 `run_gen_step_iter` 使用
475
-
476
- .. code:: python
477
-
478
- if async_:
479
- async def process():
480
- async for e in iterable:
481
- do_what_you_want()
482
- return process()
483
- else:
484
- for e in iterable:
485
- do_what_you_want()
486
-
487
- 大概相当于
488
-
489
- .. code:: python
490
-
491
- def gen_step():
492
- with with_iter_next(iterable) as do_next:
493
- while True:
494
- e = yield do_next()
495
- do_what_you_want()
496
-
497
- run_gen_step(gen_step, async_)
498
- """
499
- if isinstance(iterable, AsyncIterable):
500
- try:
501
- yield aiter(iterable).__anext__
502
- except StopAsyncIteration:
503
- pass
504
- else:
505
- try:
506
- yield iter(iterable).__next__
507
- except StopIteration:
508
- pass
509
-
510
-
511
72
  def map(
512
73
  function: None | Callable,
513
74
  iterable: Iterable | AsyncIterable,
@@ -1498,3 +1059,4 @@ def bfs_gen[T](*initials) -> Generator[None | T, T | None, None]:
1498
1059
  except IndexError:
1499
1060
  pass
1500
1061
 
1062
+ # TODO: 这个模块添加了很多不必要的函数,需要进行移除
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "python-iterutils"
3
- version = "0.2.10.2"
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"]