From 69c453ecb78ef722f8cab876516337096b6e2e90 Mon Sep 17 00:00:00 2001 From: dualfroz Date: Fri, 4 Sep 2026 09:05:45 +0200 Subject: [PATCH] fix: preserve maxlen in PDeque.remove and PDeque.reverse PDeque threads its maxlen into every structural result except remove() and reverse(), which constructed the new PDeque without the maxlen argument and thus defaulted it to None. A bounded deque therefore became unbounded after either operation, so later appends could grow it past its declared maximum length. Pass self._maxlen through in both methods to match the other operations. --- CHANGES.txt | 5 +++++ pyrsistent/_pdeque.py | 6 +++--- tests/deque_test.py | 14 ++++++++++++++ 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 3408738..3a03ec4 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,5 +1,10 @@ Revision history ---------------- +unreleased + * Fix PDeque.remove() and PDeque.reverse() dropping the maxlen of a bounded + deque, which silently unbounded the result so later appends could grow it + past its declared maximum length. + 0.21.0, 2026-01-11 * Drop official support for Python 3.8 and 3.9, these versions are no longer part of the test matrix. * Add Python 3.13 and 3.14 to test matrix and re-establish test coverage. diff --git a/pyrsistent/_pdeque.py b/pyrsistent/_pdeque.py index 0f25936..4ce7620 100644 --- a/pyrsistent/_pdeque.py +++ b/pyrsistent/_pdeque.py @@ -272,13 +272,13 @@ def remove(self, elem): pdeque([1, 2]) """ try: - return PDeque(self._left_list.remove(elem), self._right_list, self._length - 1) + return PDeque(self._left_list.remove(elem), self._right_list, self._length - 1, self._maxlen) except ValueError: # Value not found in left list, try the right list try: # This is severely inefficient with a double reverse, should perhaps implement a remove_last()? return PDeque(self._left_list, - self._right_list.reverse().remove(elem).reverse(), self._length - 1) + self._right_list.reverse().remove(elem).reverse(), self._length - 1, self._maxlen) except ValueError as e: raise ValueError('{0} not found in PDeque'.format(elem)) from e @@ -294,7 +294,7 @@ def reverse(self): >>> reversed(pdeque([1, 2, 3])) pdeque([3, 2, 1]) """ - return PDeque(self._right_list, self._left_list, self._length) + return PDeque(self._right_list, self._left_list, self._length, self._maxlen) __reversed__ = reverse def rotate(self, steps): diff --git a/tests/deque_test.py b/tests/deque_test.py index 7798a75..30b1823 100644 --- a/tests/deque_test.py +++ b/tests/deque_test.py @@ -169,6 +169,14 @@ def test_remove(): assert pdeque([1, 2, 3, 3, 4, 5, 4, 6]).remove(4) == pdeque([1, 2, 3, 3, 5, 4, 6]) +def test_remove_preserves_maxlen(): + # A bounded deque must stay bounded after a remove, or later appends grow + # it past maxlen. + x = pdeque([1, 2, 3], maxlen=3).remove(2) + assert x.maxlen == 3 + assert x.append(4).append(5) == pdeque([3, 4, 5], maxlen=3) + + def test_remove_element_missing(): with pytest.raises(ValueError): pdeque().remove(2) @@ -182,6 +190,12 @@ def test_reverse(): assert pdeque().reverse() == pdeque() +def test_reverse_preserves_maxlen(): + x = pdeque([1, 2, 3], maxlen=3).reverse() + assert x.maxlen == 3 + assert x.append(4) == pdeque([2, 1, 4], maxlen=3) + + def test_rotate_right(): assert pdeque([1, 2, 3, 4, 5]).rotate(2) == pdeque([4, 5, 1, 2, 3]) assert pdeque([1, 2]).rotate(0) == pdeque([1, 2])