hash() is not stable across runs¶
Level: 301 · for Python programmers
Stub — an outline, not a lesson. There is no runnable example behind this page yet, so nothing on it has been through the check that backs every other claim in this library. The bullets below are the questions the finished page has to answer.
One line: A str or bytes object hashes differently in every process unless PYTHONHASHSEED pins the seed, so a set of strings iterates in a different order on every run, while hash(1) is 1, hash(-1) is -2, and hash(1) == hash(1.0) == hash(True) — the numbers chapter's rule with a security fix on top.
What the finished page has to answer¶
- Why the seed exists: hash flooding, the 2011 attack that made a web server compute for minutes on one crafted request, and PEP 456's answer, a keyed SipHash with a key chosen at start-up. Which types it covers,
str,bytesanddatetime, and which it does not. - What it changes and what it leaves alone. A
dictiterates in insertion order, so it is unaffected; asetiterates in table order, soset('abc')prints differently from one run to the next. The example has to spawn child interpreters withPYTHONHASHSEED=0and=1to show the two orders side by side, because a single process cannot see its own seed change. - Why
hash(-1)is-2: the C function returns-1to signal an error, so the value is displaced by one. Whyhash(2**61)is1: integers hash modulo2**61 - 1. Whyhash(1) == hash(1.0) == hash(True) == hash(Fraction(1)), which Comparing anintwith afloatneeds for a dict to work. - Where it bites: a doctest or an answer key that prints a set of strings, a JSON file written from a set, a "stable" ordering that held on one machine. This library's own runner would catch the first case, and the page should say how.
- The contract with
__eq__, which Defining__eq__deletes__hash__owns: equal objects must hash equal, and an object whose hash changes while it is in a set is lost.
See also¶
- Comparing an
intwith afloat: section 6, equal numbers are one dict key - Defining
__eq__deletes__hash__: the contract from the class's side hash()↗ andPYTHONHASHSEED↗ in the Python docs, and PEP 456 ↗