The example in this implementation note became obsolete in 2010 (dc787d2). Numeric type implementers should study the implementations of classes like Decimal and Fraction instead of relying on this web page, so I think removing this note would be better than updating it.
That's not how Fraction.__hash__ is implemented these days. I think if the example just referenced "this is how a generic numeric type could implement __hash__", then that would be an improvement.
A generic numeric type that uses this example to implement __hash__ would not satisfy the requirements. Replacing the obsolete text and example with a helpful link would be an improvement.
A generic numeric type that uses this example to implement hash would not satisfy the requirements.
Which one will be violated, could you be more precise?
BTW, this page documents how builtin/stdlib's types are hashed. In principle, external types might implement hashing differently.
Though, if your type mathematically equivalent to some builtin/stdlib type (i.e. an alternative implementation of arbitrary-precision integers, or fractions) - probably you would like to have same hashing as CPython's types, to speedup comparisons with ones.
A generic numeric type that uses this example to implement hash would not satisfy the requirements.
Which one will be violated, could you be more precise?
BTW, this page documents how builtin/stdlib's types are hashed. In principle, external types might implement hashing differently.
Though, if your type mathematically equivalent to some builtin/stdlib type (i.e. an alternative implementation of arbitrary-precision integers, or fractions) - probably you would like to have same hashing as CPython's types, to speedup comparisons with ones.
The old example falls back to hash((numerator, denominator)) — that's tuple hashing, not the numeric hashing algorithm. So following it wouldn't make your type's hash match the invariant in the hashing-of-numeric-types docs.
The old example falls back to hash((numerator, denominator)) — that's tuple hashing, not the numeric hashing algorithm.
Why do you think so? It's perfectly fine algorithm for hashing rationals. Just not the one, used by Fraction's nowadays.
following it wouldn't make your type's hash match the invariant in the hashing-of-numeric-types docs.
Which invariant is broken because of the fallback case?
The algorithm in the example miss case for Decimal's. That violates "For numbers x and y, possibly of different types, it’s a requirement that hash(x) == hash(y) whenever x == y", though it's a different story.
I suppose a requirement presented in the context of built-in numeric types might not apply to user-defined types, but who benefits from this interpretation of the documentation? As things are, assignment to spam[1+0j] overwrites previous assignment to spam[1] even if we prefer coexistence. As things are, 1 == 1.0 == 1+0j == Decimal('1.0') == Fraction('2/2') and these objects have the same hash value.
In general, user-defined types are easier to use when their behavior is less surprising, so I would be surprised if a (hypothetical) user-defined numeric type benefits from using a different hash algorithm and suggest that we (temporariliy?) interpret this requirement as having broader scope.
I had planned to submit a pull request by now, but things happened ... happy to sit on my hands :-)
Activity
picnixz commented on Jun 27, 2026
The example is about Fraction itself. Is it now outdated? If so, we should update it, if not, we should not.
ericvsmith commented on Jun 29, 2026
That's not how Fraction.__hash__ is implemented these days. I think if the example just referenced "this is how a generic numeric type could implement __hash__", then that would be an improvement.
cossor commented on Jun 29, 2026
A generic numeric type that uses this example to implement __hash__ would not satisfy the requirements. Replacing the obsolete text and example with a helpful link would be an improvement.
skirpichev commented on Jun 29, 2026
Which one will be violated, could you be more precise?
BTW, this page documents how builtin/stdlib's types are hashed. In principle, external types might implement hashing differently.
Though, if your type mathematically equivalent to some builtin/stdlib type (i.e. an alternative implementation of arbitrary-precision integers, or fractions) - probably you would like to have same hashing as CPython's types, to speedup comparisons with ones.
I think we could add reference to the https://docs.python.org/3/library/stdtypes.html#hashing-of-numeric-types to describe contemporary implementation of hashing for Fraction's, this will serve also as an example of a correct implementation. And remove obsoleted example.
tangyuan0821 commented on Jun 29, 2026
The old example falls back to hash((numerator, denominator)) — that's tuple hashing, not the numeric hashing algorithm. So following it wouldn't make your type's hash match the invariant in the hashing-of-numeric-types docs.
skirpichev commented on Jun 29, 2026
Why do you think so? It's perfectly fine algorithm for hashing rationals. Just not the one, used by Fraction's nowadays.
Which invariant is broken because of the fallback case?
The algorithm in the example miss case for Decimal's. That violates "For numbers x and y, possibly of different types, it’s a requirement that hash(x) == hash(y) whenever x == y", though it's a different story.
Anyway, it's explained above why external numerical types might want to follow stdlib hashing algorithms (and they do, see e.g. gmpy2 or mpmath). That's why it's better to replace example with a reference to https://docs.python.org/3/library/stdtypes.html#hashing-of-numeric-types.
tangyuan0821 commented on Jun 29, 2026
Thanks, that clears it up. I could send a PR for this if nobody's working on it yet.
skirpichev commented on Jun 29, 2026
If an issue is not assigned and has no PRs - feel free work on it.
cossor commented on Jun 29, 2026
I suppose a requirement presented in the context of built-in numeric types might not apply to user-defined types, but who benefits from this interpretation of the documentation? As things are, assignment to spam[1+0j] overwrites previous assignment to spam[1] even if we prefer coexistence. As things are, 1 == 1.0 == 1+0j == Decimal('1.0') == Fraction('2/2') and these objects have the same hash value.
In general, user-defined types are easier to use when their behavior is less surprising, so I would be surprised if a (hypothetical) user-defined numeric type benefits from using a different hash algorithm and suggest that we (temporariliy?) interpret this requirement as having broader scope.
I had planned to submit a pull request by now, but things happened ... happy to sit on my hands :-)
gh-152359: Update numbers.rst to reference numeric-hash docs (#152549)
[3.15] gh-152359: Update numbers.rst to reference numeric-hash docs (G…
[3.13] gh-152359: Update numbers.rst to reference numeric-hash docs (G…
[3.14] gh-152359: Update numbers.rst to reference numeric-hash docs (G…
picnixz commented on Jun 29, 2026
Thank you for the report and fix!