python-iterutils 0.2.10.2__tar.gz → 0.3.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.4
2
2
  Name: python-iterutils
3
- Version: 0.2.10.2
3
+ Version: 0.3.1
4
4
  Summary: Python another itertools.
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -21,9 +21,10 @@ 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-argtools (>=0.0.4)
25
+ Requires-Dist: python-asynctools (>=0.2.1)
26
+ Requires-Dist: python-texttools (>=0.0.6)
27
+ Requires-Dist: python-undefined (>=0.0.4)
27
28
  Project-URL: Homepage, https://github.com/ChenyangGao/python-modules/tree/main/python-iterutils
28
29
  Project-URL: Repository, https://github.com/ChenyangGao/python-modules/tree/main/python-iterutils
29
30
  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, 1)
6
+
7
+ from .dynamic import *
8
+ from .gen_step import *
9
+ from .misc import *
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env python3
2
+ # encoding: utf-8
3
+
4
+ __all__ = ["dynamic_async", "dynamic_async_iter"]
5
+
6
+ from asyncio import AbstractEventLoop
7
+ from collections.abc import AsyncIterable, Callable, Iterable
8
+ from inspect import isawaitable
9
+
10
+ from argtools import has_keyword_arg
11
+ from asynctools import (
12
+ ensure_async, in_async, iter_async, run_async, to_coroutine,
13
+ to_aiter,
14
+ )
15
+ from decotools import optional
16
+
17
+
18
+ @optional
19
+ def dynamic_async(
20
+ func: Callable,
21
+ /,
22
+ threaded: bool = False,
23
+ async_: None | bool = None,
24
+ loop: None | AbstractEventLoop = None,
25
+ ):
26
+ has_async_arg = has_keyword_arg(func, "async_")
27
+ def wrapper(*args, async_: None | bool = async_, **kwds):
28
+ if async_ is None:
29
+ async_ = in_async()
30
+ if has_async_arg:
31
+ kwds["async_"] = async_
32
+ if async_:
33
+ if threaded:
34
+ r = ensure_async(func, threaded=threaded)(*args, **kwds)
35
+ else:
36
+ r = func(*args, **kwds)
37
+ if not isawaitable(r):
38
+ r = to_coroutine(r)
39
+ else:
40
+ r = func(*args, **kwds)
41
+ if isawaitable(r):
42
+ r = run_async(r, loop)
43
+ return r
44
+ return wrapper
45
+
46
+
47
+ @optional
48
+ def dynamic_async_iter(
49
+ func: Callable[..., Iterable] | Callable[..., AsyncIterable],
50
+ /,
51
+ threaded: bool = False,
52
+ async_: None | bool = None,
53
+ loop: None | AbstractEventLoop = None,
54
+ ):
55
+ has_async_arg = has_keyword_arg(func, "async_")
56
+ def wrapper(*args, async_: None | bool = async_, **kwds):
57
+ if async_ is None:
58
+ async_ = in_async()
59
+ if has_async_arg:
60
+ kwds["async_"] = async_
61
+ it = func(*args, **kwds)
62
+ if async_:
63
+ if not isinstance(it, AsyncIterable):
64
+ it = to_aiter(it, threaded=threaded)
65
+ elif isinstance(it, AsyncIterable):
66
+ it = iter_async(it, loop)
67
+ return it
68
+ return wrapper
69
+
@@ -0,0 +1,447 @@
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", "with_lock", "split_cm",
7
+ "Yield", "YieldFrom",
8
+ ]
9
+
10
+ from asyncio import CancelledError as AsyncCancelledError
11
+ from collections.abc import (
12
+ AsyncGenerator, AsyncIterable, AsyncIterator, Awaitable, Callable,
13
+ Coroutine, Generator, Iterable, Iterator,
14
+ )
15
+ from concurrent.futures import CancelledError
16
+ from contextlib import contextmanager, AbstractContextManager, AbstractAsyncContextManager
17
+ from dataclasses import dataclass
18
+ from inspect import isawaitable
19
+ from sys import exc_info
20
+ from typing import (
21
+ cast, overload, runtime_checkable, Any, ContextManager, Literal, Protocol,
22
+ )
23
+
24
+ from argtools import has_keyword_arg
25
+ from asynctools import in_async
26
+ from decotools import optional
27
+
28
+
29
+ @runtime_checkable
30
+ class SupportsBool(Protocol):
31
+ def __bool__(self, /) -> bool:
32
+ ...
33
+
34
+
35
+ @dataclass(slots=True, frozen=True)
36
+ class Yield:
37
+ """专供 `run_gen_step_iter`,说明值需要 yield 给用户
38
+ """
39
+ value: Any
40
+
41
+
42
+ @dataclass(slots=True, frozen=True)
43
+ class YieldFrom:
44
+ """专供 `run_gen_step_iter`,说明值需要解包后逐个 yield 给用户
45
+ """
46
+ value: Any
47
+
48
+
49
+ def iter_gen_step_sync[Y](
50
+ gen: Generator[Y, None | Y, Y],
51
+ /,
52
+ ) -> Generator[Y, Any, Any]:
53
+ send = gen.send
54
+ try:
55
+ v = None
56
+ while True:
57
+ yield (v := send(v))
58
+ except StopIteration as e:
59
+ yield e.value
60
+ finally:
61
+ gen.close()
62
+
63
+
64
+ async def iter_gen_step_async[Y](
65
+ gen: Generator[Awaitable[Y], None | Y, Awaitable[Y] | Y],
66
+ /,
67
+ ) -> AsyncGenerator[Y, Any]:
68
+ send = gen.send
69
+ throw = gen.throw
70
+ try:
71
+ r = send(None)
72
+ while True:
73
+ try:
74
+ yield (v := await r)
75
+ except BaseException as e:
76
+ r = throw(e)
77
+ else:
78
+ r = send(v)
79
+ raise AsyncCancelledError
80
+ except StopIteration as e:
81
+ v = e.value
82
+ if isawaitable(v):
83
+ v = await v
84
+ yield v
85
+ finally:
86
+ gen.close()
87
+
88
+
89
+ @overload
90
+ def iter_gen_step[Y](
91
+ gen: Generator[Y, None | Y, Y] | Callable[[], Generator[Y, None | Y, Y]],
92
+ /,
93
+ async_: Literal[False] = False,
94
+ ) -> Generator[Y, Any, Any]:
95
+ ...
96
+ @overload
97
+ def iter_gen_step[Y](
98
+ gen: Generator[Awaitable[Y], None | Y, Awaitable[Y] | Y] | Callable[[], Generator[Awaitable[Y], None | Y, Awaitable[Y] | Y]],
99
+ /,
100
+ async_: Literal[True],
101
+ ) -> AsyncGenerator[Y, Any]:
102
+ ...
103
+ def iter_gen_step[Y](
104
+ 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]],
105
+ /,
106
+ async_: Literal[False, True] = False,
107
+ ) -> Generator[Y, Any, Any] | AsyncGenerator[Y, Any]:
108
+ if not isinstance(gen, Generator):
109
+ gen = gen()
110
+ if async_:
111
+ gen = cast(Generator[Awaitable[Y], None | Y, Awaitable[Y] | Y], gen)
112
+ return iter_gen_step_async(gen)
113
+ else:
114
+ gen = cast(Generator[Y, None | Y, Y], gen)
115
+ return iter_gen_step_sync(gen)
116
+
117
+
118
+ def run_gen_step_sync[Y, T](
119
+ gen: Generator[Y, None | Y, T],
120
+ /,
121
+ running: SupportsBool = True,
122
+ ) -> T:
123
+ send = gen.send
124
+ try:
125
+ v = None
126
+ while running:
127
+ v = send(v)
128
+ raise CancelledError
129
+ except StopIteration as e:
130
+ return e.value
131
+ finally:
132
+ gen.close()
133
+
134
+
135
+ async def run_gen_step_async[Y, T](
136
+ gen: Generator[Awaitable[Y], None | Y, Awaitable[T] | T],
137
+ /,
138
+ running: SupportsBool = True,
139
+ ) -> T:
140
+ send = gen.send
141
+ throw = gen.throw
142
+ try:
143
+ r = send(None)
144
+ while running:
145
+ try:
146
+ v = await r
147
+ except BaseException as e:
148
+ r = throw(e)
149
+ else:
150
+ r = send(v)
151
+ raise AsyncCancelledError
152
+ except StopIteration as e:
153
+ v = e.value
154
+ if isawaitable(v):
155
+ v = await v
156
+ return v
157
+ finally:
158
+ gen.close()
159
+
160
+
161
+ @overload
162
+ def run_gen_step[T](
163
+ gen: Generator[Any, Any, T] | Callable[[], Generator[Any, Any, T]],
164
+ /,
165
+ async_: Literal[False] = False,
166
+ running: SupportsBool = True,
167
+ ) -> T:
168
+ ...
169
+ @overload
170
+ def run_gen_step[T](
171
+ gen: Generator[Awaitable, Any, Awaitable[T] | T] | Callable[[], Generator[Awaitable, Any, Awaitable[T] | T]],
172
+ /,
173
+ async_: Literal[True],
174
+ running: SupportsBool = True,
175
+ ) -> Coroutine[Any, Any, T]:
176
+ ...
177
+ def run_gen_step(
178
+ gen,
179
+ /,
180
+ async_: Literal[False, True] = False,
181
+ running: SupportsBool = True,
182
+ ):
183
+ if not isinstance(gen, Generator):
184
+ gen = gen()
185
+ if async_:
186
+ return run_gen_step_async(gen, running)
187
+ else:
188
+ return run_gen_step_sync(gen, running)
189
+
190
+
191
+ def run_gen_step_iter_sync(
192
+ gen: Generator,
193
+ /,
194
+ running: SupportsBool = True,
195
+ ) -> Iterator:
196
+ send = gen.send
197
+ try:
198
+ v: Any = None
199
+ while running:
200
+ v = send(v)
201
+ if isinstance(v, Yield):
202
+ v = v.value
203
+ yield v
204
+ elif isinstance(v, YieldFrom):
205
+ v = yield from v.value
206
+ raise CancelledError
207
+ except StopIteration as e:
208
+ return e.value
209
+ finally:
210
+ gen.close()
211
+
212
+
213
+ async def run_gen_step_iter_async(
214
+ gen: Generator,
215
+ /,
216
+ running: SupportsBool = True,
217
+ ) -> AsyncIterator:
218
+ send = gen.send
219
+ throw = gen.throw
220
+ try:
221
+ v = send(None)
222
+ while running:
223
+ try:
224
+ if isinstance(v, Yield):
225
+ yield_type = 1
226
+ v = v.value
227
+ elif isinstance(v, YieldFrom):
228
+ yield_type = 2
229
+ v = v.value
230
+ else:
231
+ yield_type = 0
232
+ if isawaitable(v):
233
+ v = await v
234
+ match yield_type:
235
+ case 1:
236
+ yield v
237
+ case 2:
238
+ if isinstance(v, AsyncIterable):
239
+ async for e in v:
240
+ yield e
241
+ else:
242
+ for e in v:
243
+ yield e
244
+ except BaseException as e:
245
+ v = throw(e)
246
+ else:
247
+ v = send(v)
248
+ except StopIteration as e:
249
+ v = e.value
250
+ if isawaitable(v):
251
+ v = await v
252
+ #return v
253
+ finally:
254
+ gen.close()
255
+
256
+
257
+ @overload
258
+ def run_gen_step_iter(
259
+ gen: Generator | Callable[[], Generator],
260
+ /,
261
+ async_: Literal[False] = False,
262
+ running: SupportsBool = True,
263
+ ) -> Iterator:
264
+ ...
265
+ @overload
266
+ def run_gen_step_iter(
267
+ gen: Generator | Callable[[], Generator],
268
+ /,
269
+ async_: Literal[True],
270
+ running: SupportsBool = True,
271
+ ) -> AsyncIterator:
272
+ ...
273
+ def run_gen_step_iter(
274
+ gen: Generator | Callable[[], Generator],
275
+ /,
276
+ async_: Literal[False, True] = False,
277
+ running: SupportsBool = True,
278
+ ) -> Iterator | AsyncIterator:
279
+ if not isinstance(gen, Generator):
280
+ gen = gen()
281
+ if async_:
282
+ return run_gen_step_iter_async(gen, running)
283
+ else:
284
+ return run_gen_step_iter_sync(gen, running)
285
+
286
+
287
+ @optional
288
+ def as_gen_step(
289
+ func: Callable[..., Generator],
290
+ /,
291
+ async_: None | bool = None,
292
+ is_iter: bool = False,
293
+ ):
294
+ has_async_arg = has_keyword_arg(func, "async_")
295
+ if is_iter:
296
+ run: Callable = run_gen_step_iter
297
+ else:
298
+ run = run_gen_step
299
+ def wrapper(*args, async_: None | bool = async_, **kwds):
300
+ if async_ is None:
301
+ async_ = in_async()
302
+ if has_async_arg:
303
+ kwds["async_"] = async_
304
+ return run(func(*args, **kwds), async_)
305
+ return wrapper
306
+
307
+
308
+ @overload
309
+ def with_iter_next[T](
310
+ iterable: Iterable[T],
311
+ /,
312
+ ) -> ContextManager[Callable[[], T]]:
313
+ ...
314
+ @overload
315
+ def with_iter_next[T](
316
+ iterable: AsyncIterable[T],
317
+ /,
318
+ ) -> ContextManager[Callable[[], Awaitable[T]]]:
319
+ ...
320
+ @contextmanager
321
+ def with_iter_next[T](
322
+ iterable: Iterable[T] | AsyncIterable[T],
323
+ /,
324
+ ):
325
+ """包装迭代器,以供 `run_gen_step` 和 `run_gen_step_iter` 使用
326
+
327
+ .. code:: python
328
+
329
+ if async_:
330
+ async def process():
331
+ async for e in iterable:
332
+ do_what_you_want(e)
333
+ return process()
334
+ else:
335
+ for e in iterable:
336
+ do_what_you_want(e)
337
+
338
+ 大概相当于
339
+
340
+ .. code:: python
341
+
342
+ def gen_step():
343
+ with with_iter_next(iterable) as do_next:
344
+ while True:
345
+ e = yield do_next()
346
+ do_what_you_want(e)
347
+
348
+ run_gen_step(gen_step, async_)
349
+ """
350
+ if isinstance(iterable, AsyncIterable):
351
+ try:
352
+ yield aiter(iterable).__anext__
353
+ except StopAsyncIteration:
354
+ pass
355
+ else:
356
+ try:
357
+ yield iter(iterable).__next__
358
+ except StopIteration:
359
+ pass
360
+
361
+
362
+ @contextmanager
363
+ def with_lock[T](lock, /):
364
+ """包装锁,以供 `run_gen_step` 和 `run_gen_step_iter` 使用
365
+
366
+ .. code:: python
367
+
368
+ if async_:
369
+ async def process():
370
+ async with lock:
371
+ ...
372
+ return process()
373
+ else:
374
+ with lock:
375
+ ...
376
+
377
+ 大概相当于
378
+
379
+ .. code:: python
380
+
381
+ def gen_step():
382
+ with with_lock(lock) as r:
383
+ yield r
384
+ ...
385
+
386
+ run_gen_step(gen_step, async_)
387
+ """
388
+ try:
389
+ yield lock.acquire()
390
+ finally:
391
+ lock.release()
392
+
393
+
394
+ @overload
395
+ def split_cm[T](
396
+ cm: AbstractContextManager[T],
397
+ /,
398
+ ) -> tuple[Callable[[], T], Callable[[], Any]]:
399
+ ...
400
+ @overload
401
+ def split_cm[T](
402
+ cm: AbstractAsyncContextManager[T],
403
+ /,
404
+ ) -> tuple[Callable[[], Coroutine[Any, Any, T]], Callable[[], Coroutine]]:
405
+ ...
406
+ def split_cm[T](
407
+ cm: AbstractContextManager[T] | AbstractAsyncContextManager[T],
408
+ /,
409
+ ) -> (
410
+ tuple[Callable[[], T], Callable[[], Any]] |
411
+ tuple[Callable[[], Coroutine[Any, Any, T]], Callable[[], Coroutine]]
412
+ ):
413
+ """拆分上下文管理器,以供 `run_gen_step` 和 `run_gen_step_iter` 使用
414
+
415
+ .. code:: python
416
+
417
+ if async_:
418
+ async def process():
419
+ async with cm as obj:
420
+ do_what_you_want(obj)
421
+ return process()
422
+ else:
423
+ with cm as obj:
424
+ do_what_you_want(obj)
425
+
426
+ 大概相当于
427
+
428
+ .. code:: python
429
+
430
+ def gen_step():
431
+ enter, exit = split_cm(cm)
432
+ obj = yield enter()
433
+ try:
434
+ do_what_you_want(obj)
435
+ finally:
436
+ yield exit()
437
+
438
+ run_gen_step(gen_step, async_)
439
+ """
440
+ if isinstance(cm, AbstractAsyncContextManager):
441
+ enter: Callable = cm.__aenter__
442
+ exit: Callable = cm.__aexit__
443
+ else:
444
+ enter = cm.__enter__
445
+ exit = cm.__exit__
446
+ return enter, lambda: exit(*exc_info())
447
+
@@ -1,16 +1,10 @@
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", "map", "filter", "reduce", "zip",
6
+ "chain", "chain_from_iterable", "chunked", "foreach", "through",
7
+ "flatten", "collect", "async_group_collect", "iter_unique",
14
8
  "async_iter_unique", "wrap_iter", "wrap_aiter", "peek_iter", "peek_aiter",
15
9
  "acc_step", "cut_iter", "context", "backgroud_loop", "gen_startup",
16
10
  "async_gen_startup", "do_iter", "do_aiter", "bfs_iter", "bfs_gen",
@@ -25,16 +19,12 @@ from collections.abc import (
25
19
  Iterator, Mapping, MutableMapping, MutableSet, MutableSequence,
26
20
  Sequence, ValuesView,
27
21
  )
28
- from contextlib import (
29
- asynccontextmanager, contextmanager, ExitStack, AsyncExitStack,
30
- AbstractContextManager, AbstractAsyncContextManager,
31
- )
22
+ from contextlib import asynccontextmanager, contextmanager, ExitStack, AsyncExitStack
32
23
  from copy import copy
33
- from dataclasses import dataclass
34
24
  from functools import update_wrapper
35
25
  from itertools import batched, chain as _chain, pairwise
36
- from inspect import iscoroutinefunction, signature
37
- from sys import _getframe, exc_info
26
+ from inspect import isawaitable, iscoroutinefunction
27
+ from sys import _getframe
38
28
  from _thread import start_new_thread
39
29
  from time import sleep, time
40
30
  from types import FrameType
@@ -44,47 +34,13 @@ from typing import (
44
34
 
45
35
  from asynctools import (
46
36
  async_filter, async_map, async_reduce, async_zip, async_batched,
47
- ensure_async, ensure_aiter, async_chain, collect as async_collect,
37
+ ensure_async, ensure_aiter, async_chain, async_chain_from_iterable,
38
+ async_collect, async_foreach, async_through,
48
39
  )
49
40
  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
- iterable = Iterable.__instancecheck__
84
- async_iterable = AsyncIterable.__instancecheck__
85
- isawaitable = Awaitable.__instancecheck__
86
-
87
-
88
44
  def _coalesce(vals, default=None):
89
45
  for val in vals:
90
46
  if val is not None:
@@ -108,406 +64,6 @@ def _get_async[T](back: int = 2, /, *, default: Literal[False] | T = False) -> b
108
64
  return _coalesce(iter_frams(), default)
109
65
 
110
66
 
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
67
  def map(
512
68
  function: None | Callable,
513
69
  iterable: Iterable | AsyncIterable,
@@ -671,11 +227,8 @@ def chain_from_iterable[T](
671
227
  *,
672
228
  async_: Literal[False, True] = False,
673
229
  ) -> Iterator[T] | AsyncIterator[T]:
674
- if async_ or not isinstance(iterables, Iterable):
675
- if isinstance(iterables, Iterable):
676
- return async_chain.from_iterable(iterables, threaded=threaded)
677
- else:
678
- return async_chain.from_iterable(iterables, threaded=threaded)
230
+ if async_ or threaded:
231
+ return async_chain_from_iterable(iterables, threaded=threaded)
679
232
  return _chain.from_iterable(iterables) # type: ignore
680
233
 
681
234
  setattr(chain, "from_iterable", chain_from_iterable)
@@ -730,85 +283,45 @@ def chunked[T](
730
283
 
731
284
 
732
285
  def foreach(
733
- value: Callable,
286
+ function: Callable,
734
287
  iterable: Iterable | AsyncIterable,
735
288
  /,
736
289
  *iterables: Iterable | AsyncIterable,
290
+ default = None,
737
291
  threaded: bool = False,
738
292
  ):
739
- """
740
- """
741
- if (not threaded and
742
- isinstance(iterable, Iterable) and
743
- all(isinstance(it, Iterable) for it in iterables)
293
+ if (threaded or
294
+ isinstance(iterable, AsyncIterable) or
295
+ any(isinstance(it, AsyncIterable) for it in iterables)
744
296
  ):
745
- if iterables:
746
- for args in _zip(iterable, *iterables):
747
- value(*args)
748
- else:
749
- for arg in iterable:
750
- value(arg)
751
- else:
752
- return async_foreach(value, iterable, *iterables, threaded=threaded)
753
-
754
-
755
- async def async_foreach(
756
- value: Callable,
757
- iterable: Iterable | AsyncIterable,
758
- /,
759
- *iterables: Iterable | AsyncIterable,
760
- threaded: bool = False,
761
- ):
762
- """
763
- """
764
- value = ensure_async(value, threaded=threaded)
297
+ return async_foreach(
298
+ function,
299
+ iterable,
300
+ *iterables,
301
+ default=default,
302
+ threaded=threaded,
303
+ )
304
+ r = default
765
305
  if iterables:
766
- async for args in async_zip(iterable, *iterables, threaded=threaded):
767
- await value(*args)
306
+ for args in _zip(iterable, *iterables):
307
+ r = function(*args)
768
308
  else:
769
- async for arg in ensure_aiter(iterable, threaded=threaded):
770
- await value(arg)
309
+ for arg in iterable:
310
+ r = function(arg)
311
+ return r
771
312
 
772
313
 
773
314
  def through(
774
315
  iterable: Iterable | AsyncIterable,
775
316
  /,
776
- take_while: None | Callable = None,
777
317
  threaded: bool = False,
778
318
  ):
779
319
  """
780
320
  """
781
- if threaded or not isinstance(iterable, Iterable):
782
- return async_through(iterable, take_while, threaded=threaded)
783
- elif take_while is None:
784
- for _ in iterable:
785
- pass
786
- else:
787
- for v in _map(take_while, iterable):
788
- if not v:
789
- break
790
-
791
-
792
- async def async_through(
793
- iterable: Iterable | AsyncIterable,
794
- /,
795
- take_while: None | Callable = None,
796
- threaded: bool = False,
797
- ):
798
- """
799
- """
800
- iterable = ensure_aiter(iterable, threaded=threaded)
801
- if take_while is None:
802
- async for _ in iterable:
803
- pass
804
- elif take_while is bool:
805
- async for v in iterable:
806
- if not v:
807
- break
808
- else:
809
- async for v in async_map(take_while, iterable):
810
- if not v:
811
- break
321
+ if threaded or isinstance(iterable, AsyncIterable):
322
+ return async_through(iterable, threaded=threaded)
323
+ for _ in iterable:
324
+ pass
812
325
 
813
326
 
814
327
  @overload
@@ -1498,3 +1011,4 @@ def bfs_gen[T](*initials) -> Generator[None | T, T | None, None]:
1498
1011
  except IndexError:
1499
1012
  pass
1500
1013
 
1014
+ # TODO: 这个模块添加了很多不必要的函数,需要进行移除
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "python-iterutils"
3
- version = "0.2.10.2"
3
+ version = "0.3.1"
4
4
  description = "Python another itertools."
5
5
  authors = ["ChenyangGao <wosiwujm@gmail.com>"]
6
6
  license = "MIT"
@@ -27,9 +27,10 @@ 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-argtools = ">=0.0.4"
31
+ python-asynctools = ">=0.2.1"
32
+ python-texttools = ">=0.0.6"
33
+ python-undefined = ">=0.0.4"
33
34
 
34
35
  [build-system]
35
36
  requires = ["poetry-core"]