From d8293c801bbb48914f3952bf749bacff326ada43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20K=C3=BChn?= Date: Fri, 15 Aug 2025 23:44:02 +0200 Subject: [PATCH 1/6] sentinel is None --- asyncstdlib/itertools.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/asyncstdlib/itertools.py b/asyncstdlib/itertools.py index 006da37..846f7eb 100644 --- a/asyncstdlib/itertools.py +++ b/asyncstdlib/itertools.py @@ -64,9 +64,6 @@ async def cycle(iterable: AnyIterable[T]) -> AsyncIterator[T]: yield item -__ACCUMULATE_SENTINEL = Sentinel("") - - async def add(x: ADD, y: ADD) -> ADD: """The default reduction of :py:func:`~.accumulate`""" return x + y @@ -78,7 +75,7 @@ async def accumulate( Callable[[Any, Any], Any], Callable[[Any, Any], Awaitable[Any]] ] = add, *, - initial: Any = __ACCUMULATE_SENTINEL, + initial: Any = None, ) -> AsyncIterator[Any]: """ An :term:`asynchronous iterator` on the running reduction of ``iterable`` @@ -105,11 +102,7 @@ async def accumulate(iterable, function, *, initial): """ async with ScopedIter(iterable) as item_iter: try: - value = ( - initial - if initial is not __ACCUMULATE_SENTINEL - else await anext(item_iter) - ) + value = initial if initial is not None else await anext(item_iter) except StopAsyncIteration: raise TypeError( "accumulate() of empty sequence with no initial value" From 986f33370de66c699960911c32966476887f53bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20K=C3=BChn?= Date: Fri, 15 Aug 2025 23:45:54 +0200 Subject: [PATCH 2/6] update type hints --- asyncstdlib/itertools.pyi | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/asyncstdlib/itertools.pyi b/asyncstdlib/itertools.pyi index f65ff6d..e561d57 100644 --- a/asyncstdlib/itertools.pyi +++ b/asyncstdlib/itertools.pyi @@ -16,13 +16,17 @@ from ._typing import AnyIterable, ADD, T, T1, T2, T3, T4, T5 def cycle(iterable: AnyIterable[T]) -> AsyncIterator[T]: ... @overload -def accumulate(iterable: AnyIterable[ADD]) -> AsyncIterator[ADD]: ... +def accumulate( + iterable: AnyIterable[ADD], *, initial: None = ... +) -> AsyncIterator[ADD]: ... @overload def accumulate(iterable: AnyIterable[ADD], *, initial: ADD) -> AsyncIterator[ADD]: ... @overload def accumulate( iterable: AnyIterable[T], function: Callable[[T, T], T] | Callable[[T, T], Awaitable[T]], + *, + initial: None = ..., ) -> AsyncIterator[T]: ... @overload def accumulate( From f14694b3e60c288aefd6d6eddd5c22186d688455 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20K=C3=BChn?= Date: Fri, 15 Aug 2025 23:51:35 +0200 Subject: [PATCH 3/6] document changed behaviour --- docs/source/api/itertools.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/source/api/itertools.rst b/docs/source/api/itertools.rst index 733332f..c61b750 100644 --- a/docs/source/api/itertools.rst +++ b/docs/source/api/itertools.rst @@ -68,6 +68,10 @@ Iterator transforming .. autofunction:: accumulate(iterable: (async) iter T, function: (T, T) → (await) T = add [, initial: T]) :async-for: :T + .. versionchanged:: 3.13.2 + + ``initial=None`` means no initial value is assumed. + .. autofunction:: starmap(function: (*A) → (await) T, iterable: (async) iter (A, ...)) :async-for: :T From 3daa505660fdb638d1f18b164acd2d3a9bfa9b3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20K=C3=BChn?= Date: Mon, 18 Aug 2025 20:03:43 +0200 Subject: [PATCH 4/6] test docs --- unittests/test_itertools.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/unittests/test_itertools.py b/unittests/test_itertools.py index 5f88e96..eceb96c 100644 --- a/unittests/test_itertools.py +++ b/unittests/test_itertools.py @@ -34,6 +34,7 @@ async def reduction(x, y): @sync async def test_accumulate_default(): + """Test the default function of accumulate""" for itertype in (asyncify, list): assert await a.list(a.accumulate(itertype([0, 1]))) == list( itertools.accumulate([0, 1]) @@ -53,6 +54,7 @@ async def test_accumulate_default(): @sync async def test_accumulate_misuse(): + """Test wrong arguments to accumulate""" with pytest.raises(TypeError): assert await a.list(a.accumulate([])) From f8ba1bcc892ee6cc0ce1225115b908f89daf5af1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20K=C3=BChn?= Date: Mon, 18 Aug 2025 20:08:26 +0200 Subject: [PATCH 5/6] test initial=None --- unittests/test_itertools.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/unittests/test_itertools.py b/unittests/test_itertools.py index eceb96c..82e4e7a 100644 --- a/unittests/test_itertools.py +++ b/unittests/test_itertools.py @@ -59,6 +59,16 @@ async def test_accumulate_misuse(): assert await a.list(a.accumulate([])) +@sync +async def test_accumulate_initial(): + """Test the `initial` argument to accumulate""" + assert ( + await a.list(a.accumulate(asyncify([1, 2, 3]), initial=None)) + == await a.list(a.accumulate(asyncify([1, 2, 3]))) + == list(itertools.accumulate([1, 2, 3], initial=None)) + ) + + batched_cases = [ (range(10), 2, [(0, 1), (2, 3), (4, 5), (6, 7), (8, 9)]), (range(10), 3, [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9,)]), From 27e1e86370601956c16babda36d21a60405cddf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20K=C3=BChn?= Date: Mon, 18 Aug 2025 20:11:18 +0200 Subject: [PATCH 6/6] cleanup unused import --- asyncstdlib/itertools.py | 1 - 1 file changed, 1 deletion(-) diff --git a/asyncstdlib/itertools.py b/asyncstdlib/itertools.py index 846f7eb..febd591 100644 --- a/asyncstdlib/itertools.py +++ b/asyncstdlib/itertools.py @@ -24,7 +24,6 @@ from ._core import ( ScopedIter, awaitify as _awaitify, - Sentinel, borrow as _borrow, ) from .builtins import (