Skip to content

Commit 704cebb

Browse files
committed
Let acount count down to a stop
With a negative step the stop is a lower bound, the way range reads it. acount(10, -2, stop=0) yielded nothing.
1 parent 6cbc06e commit 704cebb

2 files changed

Lines changed: 42 additions & 2 deletions

File tree

‎_python_utils_tests/test_aio.py‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,43 @@ async def mock_sleep(delay: float) -> None:
2525
assert sum(sleeps) == 4
2626

2727

28+
@pytest.mark.asyncio
29+
@pytest.mark.parametrize(
30+
'start,step,stop,expected',
31+
[
32+
(10, -2, 0, [10, 8, 6, 4, 2]),
33+
(0, -1, -3, [0, -1, -2]),
34+
(1.5, -0.5, 0, [1.5, 1.0, 0.5]),
35+
# Nothing to count when `start` is already past `stop`, in either
36+
# direction. This is what `range` does as well.
37+
(0, -1, 5, []),
38+
(5, 1, 3, []),
39+
(0, 2, 5, [0, 2, 4]),
40+
],
41+
)
42+
async def test_acount_stop_follows_step_direction(
43+
start: float, step: float, stop: float, expected: types.List[float]
44+
) -> None:
45+
"""Count down to a lower ``stop`` when the step is negative."""
46+
limit: int = len(expected) + 5
47+
items: types.List[float] = []
48+
item: float
49+
# `acount` is annotated as a plain iterator, closing takes a generator.
50+
counter: types.AsyncGenerator[float, None] = types.cast(
51+
types.AsyncGenerator[float, None],
52+
aio.acount(start=start, step=step, stop=stop),
53+
)
54+
async for item in counter:
55+
items.append(item)
56+
# A counter that misses its `stop` never ends by itself.
57+
if len(items) == limit:
58+
break
59+
60+
await counter.aclose()
61+
62+
assert items == expected
63+
64+
2865
@pytest.mark.asyncio
2966
async def test_acontainer() -> None:
3067
"""Collect an async iterable into the requested container."""

‎python_utils/aio.py‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ async def acount(
3535
start: First value to yield.
3636
step: Amount added between successive values.
3737
delay: Seconds to ``asyncio.sleep`` between yields.
38-
stop: Exclusive upper bound; ``None`` counts forever.
38+
stop: Exclusive bound, ``None`` counts forever. It is an upper bound
39+
for a positive ``step`` and a lower bound for a negative one, the
40+
way ``range`` reads it.
3941
4042
Yields:
4143
The successive counter values.
@@ -46,7 +48,8 @@ async def acount(
4648
[0, 1, 2]
4749
"""
4850
for item in itertools.count(start, step): # pragma: no branch
49-
if stop is not None and item >= stop:
51+
# A negative step counts down, so `stop` is the lower bound there.
52+
if stop is not None and (item <= stop if step < 0 else item >= stop):
5053
break
5154

5255
yield item

0 commit comments

Comments
 (0)