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.
- {python_iterutils-0.2.10.1 → python_iterutils-0.3.0}/PKG-INFO +8 -6
- python_iterutils-0.3.0/iterutils/__init__.py +9 -0
- python_iterutils-0.3.0/iterutils/dynamic.py +102 -0
- python_iterutils-0.3.0/iterutils/gen_step.py +414 -0
- python_iterutils-0.2.10.1/iterutils/__init__.py → python_iterutils-0.3.0/iterutils/misc.py +110 -495
- {python_iterutils-0.2.10.1 → python_iterutils-0.3.0}/pyproject.toml +4 -4
- {python_iterutils-0.2.10.1 → python_iterutils-0.3.0}/LICENSE +0 -0
- {python_iterutils-0.2.10.1 → python_iterutils-0.3.0}/iterutils/py.typed +0 -0
- {python_iterutils-0.2.10.1 → python_iterutils-0.3.0}/readme.md +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: python-iterutils
|
|
3
|
-
Version: 0.
|
|
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.
|
|
24
|
-
Requires-Dist: python-texttools (>=0.0.
|
|
25
|
-
Requires-Dist: python-undefined (>=0.0.
|
|
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,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
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"
|
|
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", "
|
|
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,
|
|
23
|
-
Collection, Container, Coroutine, Generator, Iterable,
|
|
24
|
-
Mapping, MutableMapping, MutableSet, MutableSequence,
|
|
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
|
|
25
|
+
from functools import update_wrapper
|
|
33
26
|
from itertools import batched, chain as _chain, pairwise
|
|
34
|
-
from inspect import iscoroutinefunction
|
|
35
|
-
from sys import _getframe
|
|
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:
|
|
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:
|
|
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.
|
|
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.
|
|
31
|
-
python-texttools = ">=0.0.
|
|
32
|
-
python-undefined = ">=0.0.
|
|
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"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|