python-iterutils 0.0.9__tar.gz → 0.1.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,28 +1,26 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-iterutils
3
- Version: 0.0.9
3
+ Version: 0.1.1
4
4
  Summary: Python another itertools.
5
5
  Home-page: https://github.com/ChenyangGao/web-mount-packs/tree/main/python-module/python-iterutils
6
6
  License: MIT
7
7
  Keywords: iterable,iterutils
8
8
  Author: ChenyangGao
9
9
  Author-email: wosiwujm@gmail.com
10
- Requires-Python: >=3.10,<4.0
10
+ Requires-Python: >=3.12,<4.0
11
11
  Classifier: Development Status :: 5 - Production/Stable
12
12
  Classifier: Intended Audience :: Developers
13
13
  Classifier: License :: OSI Approved :: MIT License
14
14
  Classifier: Operating System :: OS Independent
15
15
  Classifier: Programming Language :: Python
16
16
  Classifier: Programming Language :: Python :: 3
17
- Classifier: Programming Language :: Python :: 3.10
18
- Classifier: Programming Language :: Python :: 3.11
19
17
  Classifier: Programming Language :: Python :: 3.12
20
18
  Classifier: Programming Language :: Python :: 3.13
21
19
  Classifier: Programming Language :: Python :: 3 :: Only
22
20
  Classifier: Topic :: Software Development
23
21
  Classifier: Topic :: Software Development :: Libraries
24
22
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
25
- Requires-Dist: python-asynctools (>=0.0.8.2)
23
+ Requires-Dist: python-asynctools (>=0.0.9)
26
24
  Project-URL: Repository, https://github.com/ChenyangGao/web-mount-packs/tree/main/python-module/python-iterutils
27
25
  Description-Content-Type: text/markdown
28
26
 
@@ -2,26 +2,27 @@
2
2
  # encoding: utf-8
3
3
 
4
4
  __author__ = "ChenyangGao <https://chenyanggao.github.io>"
5
- __version__ = (0, 0, 9)
5
+ __version__ = (0, 1, 1)
6
6
  __all__ = [
7
- "iterable", "async_iterable", "foreach", "async_foreach", "through", "async_through",
8
- "wrap_iter", "wrap_aiter", "acc_step", "cut_iter", "run_gen_step", "run_gen_step_iter",
9
- "Return", "Yield", "YieldFrom",
7
+ "Return", "Yield", "YieldFrom", "iterable", "async_iterable", "foreach", "async_foreach",
8
+ "through", "async_through", "flatten", "async_flatten", "chunked", "wrap_iter", "wrap_aiter",
9
+ "acc_step", "cut_iter", "run_gen_step", "run_gen_step_iter", "bfs_gen",
10
10
  ]
11
11
 
12
+
12
13
  from abc import ABC, abstractmethod
13
14
  from asyncio import to_thread
15
+ from collections import deque
14
16
  from collections.abc import (
15
- AsyncIterable, AsyncIterator, Callable, Generator, Iterable, Iterator,
17
+ AsyncIterable, AsyncIterator, Buffer, Callable, Generator,
18
+ Iterable, Iterator, Sequence,
16
19
  )
17
20
  from dataclasses import dataclass
21
+ from itertools import batched, pairwise
18
22
  from inspect import isawaitable
19
- from typing import overload, Any, Literal, TypeVar
20
-
21
- from asynctools import async_map, async_zip, ensure_async, ensure_aiter
22
-
23
+ from typing import overload, Any, Literal
23
24
 
24
- T = TypeVar("T")
25
+ from asynctools import async_map, async_zip, async_batched, ensure_async, ensure_aiter
25
26
 
26
27
 
27
28
  @dataclass(slots=True, frozen=True)
@@ -48,21 +49,28 @@ class YieldFrom(YieldBase):
48
49
  yield_type = 2
49
50
 
50
51
 
51
- def iterable(it, /) -> bool:
52
+ def iterable(iterable, /) -> bool:
52
53
  try:
53
- return isinstance(iter(it), Iterable)
54
+ return isinstance(iter(iterable), Iterable)
54
55
  except TypeError:
55
56
  return False
56
57
 
57
58
 
58
- def async_iterable(it, /) -> bool:
59
+ def async_iterable(iterable, /) -> bool:
59
60
  try:
60
- return isinstance(iter(it), AsyncIterable)
61
+ return isinstance(iter(iterable), AsyncIterable)
61
62
  except TypeError:
62
63
  return False
63
64
 
64
65
 
65
- def foreach(value: Callable, iterable, /, *iterables):
66
+ def foreach(
67
+ value: Callable,
68
+ iterable: Iterable | AsyncIterable,
69
+ /,
70
+ *iterables: Iterable | AsyncIterable,
71
+ ):
72
+ if not (isinstance(iterable, Iterable) and all(isinstance(it, Iterable) for it in iterables)):
73
+ return async_foreach(value, iterable, *iterables)
66
74
  if iterables:
67
75
  for args in zip(iterable, *iterables):
68
76
  value(*args)
@@ -71,7 +79,13 @@ def foreach(value: Callable, iterable, /, *iterables):
71
79
  value(arg)
72
80
 
73
81
 
74
- async def async_foreach(value: Callable, iterable, /, *iterables, threaded: bool = True):
82
+ async def async_foreach(
83
+ value: Callable,
84
+ iterable: Iterable | AsyncIterable,
85
+ /,
86
+ *iterables: Iterable | AsyncIterable,
87
+ threaded: bool = True,
88
+ ):
75
89
  value = ensure_async(value, threaded=threaded)
76
90
  if iterables:
77
91
  async for args in async_zip(iterable, *iterables, threaded=threaded):
@@ -81,76 +95,188 @@ async def async_foreach(value: Callable, iterable, /, *iterables, threaded: bool
81
95
  await value(arg)
82
96
 
83
97
 
84
- def through(it: Iterable, /, take_while: None | Callable = None):
98
+ def through(
99
+ iterable: Iterable | AsyncIterable,
100
+ /,
101
+ take_while: None | Callable = None,
102
+ ):
103
+ if not isinstance(iterable, Iterable):
104
+ return async_through(iterable, take_while)
85
105
  if take_while is None:
86
- for _ in it:
106
+ for _ in iterable:
87
107
  pass
88
108
  else:
89
- for v in map(take_while, it):
109
+ for v in map(take_while, iterable):
90
110
  if not v:
91
111
  break
92
112
 
93
113
 
94
114
  async def async_through(
95
- it: Iterable | AsyncIterable,
115
+ iterable: Iterable | AsyncIterable,
96
116
  /,
97
117
  take_while: None | Callable = None,
98
118
  threaded: bool = True,
99
119
  ):
100
- it = ensure_aiter(it, threaded=threaded)
120
+ iterable = ensure_aiter(iterable, threaded=threaded)
101
121
  if take_while is None:
102
- async for _ in it:
122
+ async for _ in iterable:
103
123
  pass
104
124
  elif take_while is bool:
105
- async for v in it:
125
+ async for v in iterable:
106
126
  if not v:
107
127
  break
108
128
  else:
109
- async for v in async_map(take_while, it):
129
+ async for v in async_map(take_while, iterable):
110
130
  if not v:
111
131
  break
112
132
 
113
133
 
114
- def wrap_iter(
115
- it: Iterable[T],
134
+ @overload
135
+ def flatten(
136
+ iterable: Iterable,
137
+ /,
138
+ exclude_types: type | tuple[type, ...] = (Buffer, str),
139
+ ) -> Iterable:
140
+ ...
141
+ @overload
142
+ def flatten(
143
+ iterable: AsyncIterable,
144
+ /,
145
+ exclude_types: type | tuple[type, ...] = (Buffer, str),
146
+ ) -> AsyncIterable:
147
+ ...
148
+ def flatten(
149
+ iterable: Iterable | AsyncIterable,
150
+ /,
151
+ exclude_types: type | tuple[type, ...] = (Buffer, str),
152
+ ) -> Iterable | AsyncIterable:
153
+ if not isinstance(iterable, Iterable):
154
+ return async_flatten(iterable, exclude_types)
155
+ def gen(iterable):
156
+ for e in iterable:
157
+ if isinstance(e, (Iterable, AsyncIterable)) and not isinstance(e, exclude_types):
158
+ yield from gen(e)
159
+ else:
160
+ yield e
161
+ return gen(iterable)
162
+
163
+
164
+ async def async_flatten(
165
+ iterable: Iterable | AsyncIterable,
166
+ /,
167
+ exclude_types: type | tuple[type, ...] = (Buffer, str),
168
+ threaded: bool = True,
169
+ ) -> AsyncIterator:
170
+ async for e in ensure_aiter(iterable, threaded=threaded):
171
+ if isinstance(e, (Iterable, AsyncIterable)) and not isinstance(e, exclude_types):
172
+ async for e in async_flatten(e, exclude_types, threaded=threaded):
173
+ yield e
174
+ else:
175
+ yield e
176
+
177
+
178
+ @overload
179
+ def chunked[T](
180
+ iterable: Iterable[T],
181
+ n: int = 1,
182
+ /,
183
+ ) -> Iterator[Sequence[T]]:
184
+ ...
185
+ @overload
186
+ def chunked[T](
187
+ iterable: AsyncIterable[T],
188
+ n: int = 1,
189
+ /,
190
+ ) -> AsyncIterator[Sequence[T]]:
191
+ ...
192
+ def chunked[T](
193
+ iterable: Iterable[T] | AsyncIterable[T],
194
+ n: int = 1,
195
+ /,
196
+ ) -> Iterator[Sequence[T]] | AsyncIterator[Sequence[T]]:
197
+ if n < 0:
198
+ n = 1
199
+ if isinstance(iterable, Sequence):
200
+ if n == 1:
201
+ return ((e,) for e in iterable)
202
+ return (iterable[i:j] for i, j in pairwise(range(0, len(iterable)+n, n)))
203
+ elif isinstance(iterable, Iterable):
204
+ return batched(iterable, n)
205
+ else:
206
+ return async_batched(iterable, n)
207
+
208
+
209
+ @overload
210
+ def wrap_iter[T](
211
+ iterable: Iterable[T],
116
212
  /,
117
213
  callprev: None | Callable[[T], Any] = None,
118
214
  callnext: None | Callable[[T], Any] = None,
119
215
  callenter: None | Callable[[Iterable[T]], Any] = None,
120
216
  callexit: None | Callable[[Iterable[T], None | BaseException], Any] = None,
121
217
  ) -> Iterator[T]:
218
+ ...
219
+ @overload
220
+ def wrap_iter[T](
221
+ iterable: AsyncIterable[T],
222
+ /,
223
+ callprev: None | Callable[[T], Any] = None,
224
+ callnext: None | Callable[[T], Any] = None,
225
+ callenter: None | Callable[[Iterable[T] | AsyncIterable[T]], Any] = None,
226
+ callexit: None | Callable[[Iterable[T] | AsyncIterable[T], None | BaseException], Any] = None,
227
+ ) -> AsyncIterator[T]:
228
+ ...
229
+ def wrap_iter[T](
230
+ iterable: Iterable[T] | AsyncIterable[T],
231
+ /,
232
+ callprev: None | Callable[[T], Any] = None,
233
+ callnext: None | Callable[[T], Any] = None,
234
+ callenter: None | Callable[[Iterable[T]], Any] | Callable[[Iterable[T] | AsyncIterable[T]], Any] = None,
235
+ callexit: ( None | Callable[[Iterable[T], None | BaseException], Any] |
236
+ Callable[[Iterable[T] | AsyncIterable[T], None | BaseException], Any] ) = None,
237
+ ) -> Iterator[T] | AsyncIterator[T]:
238
+ if not isinstance(iterable, Iterable):
239
+ return wrap_aiter(
240
+ iterable,
241
+ callprev=callprev,
242
+ callnext=callnext,
243
+ callenter=callenter, # type: ignore
244
+ callexit=callexit, # type: ignore
245
+ )
122
246
  if not callable(callprev):
123
247
  callprev = None
124
248
  if not callable(callnext):
125
249
  callnext = None
126
- try:
127
- if callable(callenter):
128
- callenter(it)
129
- for e in it:
130
- if callprev:
131
- try:
132
- callprev(e)
133
- except (StopIteration, GeneratorExit):
134
- break
135
- yield e
136
- if callnext:
137
- try:
138
- callnext(e)
139
- except (StopIteration, GeneratorExit):
140
- break
141
- except BaseException as e:
142
- if callable(callexit):
143
- if not callexit(it, e):
250
+ def gen():
251
+ try:
252
+ if callable(callenter):
253
+ callenter(iterable)
254
+ for e in iterable:
255
+ if callprev:
256
+ try:
257
+ callprev(e)
258
+ except (StopIteration, GeneratorExit):
259
+ break
260
+ yield e
261
+ if callnext:
262
+ try:
263
+ callnext(e)
264
+ except (StopIteration, GeneratorExit):
265
+ break
266
+ except BaseException as e:
267
+ if callable(callexit):
268
+ if not callexit(iterable, e):
269
+ raise
270
+ else:
144
271
  raise
145
- else:
146
- raise
147
- finally:
148
- if callable(callexit):
149
- callexit(it, None)
272
+ finally:
273
+ if callable(callexit):
274
+ callexit(iterable, None)
275
+ return gen()
150
276
 
151
277
 
152
- async def wrap_aiter(
153
- it: Iterable[T] | AsyncIterable[T],
278
+ async def wrap_aiter[T](
279
+ iterable: Iterable[T] | AsyncIterable[T],
154
280
  /,
155
281
  callprev: None | Callable[[T], Any] = None,
156
282
  callnext: None | Callable[[T], Any] = None,
@@ -161,7 +287,7 @@ async def wrap_aiter(
161
287
  callprev = ensure_async(callprev, threaded=threaded) if callable(callprev) else None
162
288
  callnext = ensure_async(callnext, threaded=threaded) if callable(callnext) else None
163
289
  try:
164
- async for e in ensure_aiter(it, threaded=threaded):
290
+ async for e in ensure_aiter(iterable, threaded=threaded):
165
291
  if callprev:
166
292
  try:
167
293
  await callprev(e)
@@ -175,13 +301,13 @@ async def wrap_aiter(
175
301
  break
176
302
  except BaseException as e:
177
303
  if callable(callexit):
178
- if not await ensure_async(callexit, threaded=threaded)(it, e):
304
+ if not await ensure_async(callexit, threaded=threaded)(iterable, e):
179
305
  raise
180
306
  else:
181
307
  raise
182
308
  finally:
183
309
  if callable(callexit):
184
- await ensure_async(callexit, threaded=threaded)(it, None)
310
+ await ensure_async(callexit, threaded=threaded)(iterable, None)
185
311
 
186
312
 
187
313
  def acc_step(
@@ -210,7 +336,7 @@ def cut_iter(
210
336
  yield stop, stop - start
211
337
 
212
338
 
213
- def run_gen_step(
339
+ def run_gen_step[T](
214
340
  gen_step: Generator[Any, Any, T] | Callable[[], Generator[Any, Any, T]],
215
341
  *,
216
342
  async_: bool = False,
@@ -272,16 +398,16 @@ def run_gen_step(
272
398
  result = process()
273
399
  if as_iter:
274
400
  async def wrap(result):
275
- it = await result
401
+ iterable = await result
276
402
  try:
277
- it = aiter(it)
403
+ iterable = aiter(iterable)
278
404
  except TypeError:
279
- for val in iter(it):
405
+ for val in iter(iterable):
280
406
  if isawaitable(val):
281
407
  val = await val
282
408
  yield val
283
409
  else:
284
- async for val in it:
410
+ async for val in iterable:
285
411
  yield val
286
412
  result = wrap(result)
287
413
  return result
@@ -440,3 +566,45 @@ def run_gen_step_iter(
440
566
  close()
441
567
  return process()
442
568
 
569
+
570
+ @overload
571
+ def bfs_gen[T](
572
+ initial: T,
573
+ /,
574
+ unpack_iterator: Literal[False] = False,
575
+ ) -> Generator[T, T | None, None]:
576
+ ...
577
+ @overload
578
+ def bfs_gen[T](
579
+ initial: T | Iterator[T],
580
+ /,
581
+ unpack_iterator: Literal[True],
582
+ ) -> Generator[T, T | None, None]:
583
+ ...
584
+ def bfs_gen[T](
585
+ initial: T | Iterator[T],
586
+ /,
587
+ unpack_iterator: bool = False,
588
+ ) -> Generator[T, T | None, None]:
589
+ """辅助函数,返回生成器,用来简化广度优先遍历
590
+ """
591
+ dq: deque[T] = deque()
592
+ push, pushmany, pop = dq.append, dq.extend, dq.popleft
593
+ if isinstance(initial, Iterator) and unpack_iterator:
594
+ pushmany(initial)
595
+ else:
596
+ push(initial) # type: ignore
597
+ while dq:
598
+ args: None | T = yield (val := pop())
599
+ if unpack_iterator:
600
+ while args is not None:
601
+ if isinstance(args, Iterator):
602
+ pushmany(args)
603
+ else:
604
+ push(args)
605
+ args = yield val
606
+ else:
607
+ while args is not None:
608
+ push(args)
609
+ args = yield val
610
+
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "python-iterutils"
3
- version = "0.0.9"
3
+ version = "0.1.1"
4
4
  description = "Python another itertools."
5
5
  authors = ["ChenyangGao <wosiwujm@gmail.com>"]
6
6
  license = "MIT"
@@ -13,7 +13,7 @@ classifiers = [
13
13
  "Development Status :: 5 - Production/Stable",
14
14
  "Programming Language :: Python",
15
15
  "Programming Language :: Python :: 3",
16
- "Programming Language :: Python :: 3.10",
16
+ "Programming Language :: Python :: 3.12",
17
17
  "Programming Language :: Python :: 3 :: Only",
18
18
  "Operating System :: OS Independent",
19
19
  "Intended Audience :: Developers",
@@ -26,8 +26,8 @@ include = [
26
26
  ]
27
27
 
28
28
  [tool.poetry.dependencies]
29
- python = "^3.10"
30
- python-asynctools = ">=0.0.8.2"
29
+ python = "^3.12"
30
+ python-asynctools = ">=0.0.9"
31
31
 
32
32
  [build-system]
33
33
  requires = ["poetry-core"]