From 0e3c9acb13ba9846483a4f09f3f823c169ecb042 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Ram=C3=ADrez=20Mondrag=C3=B3n?= Date: Fri, 24 Jul 2026 15:44:17 -0600 Subject: [PATCH] refactor: Simplify `expo`, `decay`, and `fibo` wait generators MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace per-iteration recomputation (`factor * base_n`, `initial_value * e**(-t*decay_factor)`, the `if/else` branch in `fibo`) with a loop that grows the value multiplicatively until it crosses the cap, then falls into an unconditional loop yielding the capped value. Avoids recomputing the closed-form expression on every yield. Since `decay`'s multiplicative accumulation can drift slightly from the old direct power computation, the corresponding tests now compare with `pytest.approx` instead of exact equality; this also replaces the file-wide float-equality ruff suppression with per-assertion `approx` calls. Signed-off-by: Edgar Ramírez Mondragón --- CHANGELOG.md | 1 + backoff/_wait_gen.py | 44 +++++++++++++++++------------------------- tests/test_wait_gen.py | 16 +++++++-------- 3 files changed, 27 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a2cc998..9fa7a4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ ### Changed - Python 3.9+ is required [#152](https://github.com/python-backoff/backoff/pull/152) +- Simplify `expo`, `decay` and `fibo` wait generators [#183](https://github.com/python-backoff/backoff/pull/183) ### Fixed diff --git a/backoff/_wait_gen.py b/backoff/_wait_gen.py index 48e555d..1231c5e 100644 --- a/backoff/_wait_gen.py +++ b/backoff/_wait_gen.py @@ -24,14 +24,13 @@ def expo( """ # Advance past initial .send() call yield 0 - base_n: float = 1 + + a = factor + while max_value is None or a < max_value: + yield a + a *= base while True: - a = factor * base_n - if max_value is None or a < max_value: - yield a - base_n *= base - else: - yield max_value + yield max_value def decay( @@ -52,14 +51,13 @@ def decay( """ # Advance past initial .send() call yield 0 - t = 0 + a = initial_value + min_value = min_value or 0.0 + while a > min_value: + yield a + a *= math.exp(-decay_factor) while True: - a = initial_value * math.e ** (-t * decay_factor) - if min_value is None or a > min_value: - yield a - t += 1 - else: - yield min_value + yield min_value def fibo(max_value: int | None = None) -> Generator[int, Any, None]: @@ -75,17 +73,14 @@ def fibo(max_value: int | None = None) -> Generator[int, Any, None]: a = 1 b = 1 + while max_value is None or a < max_value: + yield a + a, b = b, a + b while True: - if max_value is None or a < max_value: - yield a - a, b = b, a + b - else: - yield max_value + yield max_value -def constant( - interval: float | Iterable[float] = 1, -) -> Generator[int | float, Any, None]: +def constant(interval: float | Iterable[float] = 1) -> Generator[float, Any, None]: """Generator for constant intervals. Args: @@ -104,10 +99,7 @@ def constant( yield val -def runtime( - *, - value: Callable[[Any], float], -) -> Generator[float, Any, None]: +def runtime(*, value: Callable[[Any], float]) -> Generator[float, Any, None]: """Generator that is based on parsing the return value or thrown exception of the decorated method diff --git a/tests/test_wait_gen.py b/tests/test_wait_gen.py index d2fc238..67cc2ba 100644 --- a/tests/test_wait_gen.py +++ b/tests/test_wait_gen.py @@ -1,7 +1,7 @@ -# ruff: file-ignore[float-equality-comparison] - import math +import pytest + import backoff @@ -9,28 +9,28 @@ def test_decay() -> None: gen = backoff.decay() gen.send(None) for i in range(10): - assert math.e**-i == next(gen) + assert math.e**-i == pytest.approx(next(gen)) def test_decay_init100() -> None: gen = backoff.decay(initial_value=100) gen.send(None) for i in range(10): - assert 100 * math.e**-i == next(gen) + assert 100 * math.e**-i == pytest.approx(next(gen)) def test_decay_init100_decay3() -> None: gen = backoff.decay(initial_value=100, decay_factor=3) gen.send(None) for i in range(10): - assert 100 * math.e ** (-i * 3) == next(gen) + assert 100 * math.e ** (-i * 3) == pytest.approx(next(gen)) def test_decay_init100_decay3_min5() -> None: gen = backoff.decay(initial_value=100, decay_factor=3, min_value=5) gen.send(None) for i in range(10): - assert max(100 * math.e ** (-i * 3), 5) == next(gen) + assert max(100 * math.e ** (-i * 3), 5) == pytest.approx(next(gen)) def test_expo() -> None: @@ -102,8 +102,8 @@ def test_constant() -> None: gen = backoff.constant(interval=[1, 2.0, 3.25]) gen.send(None) assert next(gen) == 1 - assert next(gen) == 2.0 - assert next(gen) == 3.25 + assert next(gen) == pytest.approx(2.0) + assert next(gen) == pytest.approx(3.25) assert next(gen, None) is None