From 600beffecf2ebd186718800de0490c17da02349c Mon Sep 17 00:00:00 2001 From: Shahadat Islam Koyel <179203101+ShahadatTest@users.noreply.github.com> Date: Tue, 27 Oct 2026 17:06:20 +0600 Subject: [PATCH] Allow UniqueList self-assignment for non-reflexive values --- _python_utils_tests/test_containers.py | 28 ++++++++++++++++++++++++++ python_utils/containers.py | 11 +++++++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/_python_utils_tests/test_containers.py b/_python_utils_tests/test_containers.py index f1c6527..c65c02c 100644 --- a/_python_utils_tests/test_containers.py +++ b/_python_utils_tests/test_containers.py @@ -1209,3 +1209,31 @@ def test_unique_list_failed_assignment_takes_membership_back() -> None: assert 1 in values values.append(3) assert values == [1, 2, 3] + + +@pytest.mark.parametrize('index', [0, -1]) +@pytest.mark.parametrize('mode', ['ignore', 'raise']) +def test_unique_list_nan_self_assignment( + index: int, mode: containers.OnDuplicate +) -> None: + """Assigning the same NaN object does not introduce a duplicate.""" + missing = float('nan') + values: containers.UniqueList[float] = containers.UniqueList( + missing, on_duplicate=mode + ) + values[index] = missing + assert len(values) == 1 + assert values[index] is missing + assert missing in values + + +def test_unique_list_nan_duplicate_in_another_slot() -> None: + """The same NaN object remains a duplicate in a different list slot.""" + missing = float('nan') + values: containers.UniqueList[float] = containers.UniqueList( + 1.0, missing, on_duplicate='raise' + ) + with pytest.raises(ValueError, match='Duplicate value'): + values[0] = missing + assert values[0] == 1.0 + assert values[1] is missing diff --git a/python_utils/containers.py b/python_utils/containers.py index 48bc14e..e51e64a 100644 --- a/python_utils/containers.py +++ b/python_utils/containers.py @@ -554,6 +554,15 @@ class UniqueList(list[HT]): >>> l [5, 10, 2, 3, 4] + Assigning an item to itself keeps it, including a NaN that does not + compare equal to itself: + + >>> missing = float('nan') + >>> samples = UniqueList(missing, on_duplicate='raise') + >>> samples[0] = missing + >>> len(samples) + 1 + A value that was removed or replaced can be added again: >>> l = UniqueList(1, 2, 3) @@ -952,7 +961,7 @@ def _set_index(self, index: typing.SupportsIndex, value: HT) -> None: """ old_value: HT = self[index] known: bool = value in self._set - if known and value != old_value: + if known and value is not old_value and value != old_value: if self.on_duplicate == 'raise': raise ValueError(f'Duplicate value: {value}') else: