Skip to main content

Stash: stable hash and object stash

Stash assigns a stable hash to arbitrary Python objects, mutable or immutable, based on its state at the time of hashing.

>>> import stash
>>> d = [{1: 2}, 3]
>>> h = stash.hash(d)
>>> h.hex()
'acb6b358dde6ee740b18dff8232cce8f'

Why not use Python's built-in hash?

Python's hash differs from the stash hash in three important ways: 1. it is supported only by a limited class of immutable objects; 2. it is not stable between restarts, i.e. after restarting Python the same object may be assigned a different hash, and 3. Python promises only that an equal objects have equal hashes. Stash also guarantees the converse: equal hashes imply some notion of object equality.

Don't make promises you cannot keep.

You're referring to the fact that since we are mapping an infinite space of potential objects to a finite space of hashes, there are bound to be collisions. This is true. But by keeping track of previously seen objects we can guarantee uniqueness within that set. If a collision happens we raise an exception rather than return a colliding hash.

When a collision happens.

This is the question. By using a 128 bit hash function with good distribution properties (we use cityhash) the chance of a collision occuring is exceedingly small. To quantify this: at 128 bits it takes an input set of 18 quintillion (2^64) objects for the expected number of collisions to reach 1. This makes it permissible to make collisions an unrecoverable error in most applications.

What is the main use case?

Caching. If the output of a function is determined entirely by its arguments, then it may be worthwhile to hold on to this value in case the function is called with the same set of arguments later. However, this means having to make potentially expensive deep comparisons to all previously seen arguments every time we call the function. Worse, it also means having to make deep copies of all the arguments to protect against future external mutations. All of this is solved by making a hash of the arguments, and comparing it against earlier hashes, which is precicely what stash provides.

How does it work?

In short, stash serializes an object to bytes and hashes the serialization.

Wait, can't we just hash a pickle stream then?

Well, yes. But pickle stores more than what you are likely interested in, such as the insertion order of dictionaries, so that {'a': 1, 'b': 2} and {'b': 2, 'a': 1} would end up receiving different hashes resulting in a cache miss. Likewise, objects that contain multiple references to an object receive a different hash than one references multiple copies. Stash loosely follows Python's equality operator to decide which objects are assigned a unique hash.

Loosely?

There is a fundamental problem with objects that do not test equal to themselves, such as float('nan'): since the assigned hash is equal to itself, we cannot identify object equality with hash equality. It is also not possible to honour user defined __eq__ methods, so we go by state instead. Lastly there is an issue with True, 1 and 1.0 all testing equal. This one is not fundamental, as we could very well assign all these objects the same hash, but it adds some overhead, to no clear benefit as it is not at all given that functions treat these objects the same. So here we make the pragmatic choice of not doing the extra work.

Can you say a bit more about how this works internally?

Stash works by recursively reducing an object and stashing the components, which directly explains how common values are deduplicated: stashing the same object twice simply returns a reference to an existing hash entry. The resulting collection of hashes is bundled and hashed to form the hash of the object, Merkle tree-style. A detailed overview of the protocol can be found here.

Reducing objects recursively sounds slow. Is it slow?

Stash is implemented in rust for minimum overhead. It also keeps track of object ids seen before during serialization, to avoid recursing into the same object several times over. Stash is faster than pickle without collision checks, or roughly half as fast with in-memory collision checks.

This all sounds great. Can I use it yet?

Better not. The project is under active development and the protocol not finalized, so none of the stability guarantees are worth much yet. Hopefully soon though! Watch this space for releases to stay up to date.

Release files for stash 0.3.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for stash 0.3.1
File Size Uploaded
stash-0.3.1.tar.gz 18.7 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for stash 0.3.1
File
stash-0.3.1-cp38-abi3-win_amd64.whl CPython 3.8 abi3 Windows x86-64 Details
stash-0.3.1-cp38-abi3-win32.whl CPython 3.8 abi3 Windows x86-32 Details
stash-0.3.1-cp38-abi3-musllinux_1_1_x86_64.whl CPython 3.8 abi3 Linux musl 1.1+ x86-64 Details
stash-0.3.1-cp38-abi3-musllinux_1_1_aarch64.whl CPython 3.8 abi3 Linux musl 1.1+ ARM64 Details
stash-0.3.1-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.8 abi3 Linux glibc 2.17+ x86-64 Details
stash-0.3.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.8 abi3 Linux glibc 2.17+ ARM64 Details
stash-0.3.1-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl CPython 3.8 abi3 macOS 10.12+ universal2 (ARM64, x86-64), macOS 11.0+ ARM64, macOS 10.12+ x86-64 Details

Total release size: 2.8 MB

Release files / stash-0.3.1.tar.gz

Download URL stash-0.3.1.tar.gz
Size 18.7 kB
Tags Source
SHA-256 checksum
How to use checksums
10f3a81b058d5b8547ad523ccaff45738e4563d736e817267211dbae8e66e173
BLAKE2b-256 checksum
How to use checksums
ec3434a6a455bfafb42aadb605f46b3ad8c30e50591fd80f3c19745edd67a194
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.7.20

Release files / stash-0.3.1-cp38-abi3-win_amd64.whl

Download URL stash-0.3.1-cp38-abi3-win_amd64.whl
Size 209.0 kB
Tags CPython 3.8 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
ffc7946092557023ca554673f6f7c03d99293451998ee705c1df94f5d2cca809
BLAKE2b-256 checksum
How to use checksums
4143819e077dc3d1b9c392d873c7c405753668e969d300aad8b1052faaf24b01
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.7.20

Release files / stash-0.3.1-cp38-abi3-win32.whl

Download URL stash-0.3.1-cp38-abi3-win32.whl
Size 201.0 kB
Tags CPython 3.8 Windows x86-32 abi3
SHA-256 checksum
How to use checksums
6556ee273101e99e121bd0e130312b8d7133d159288a36c055cfe78bcd09a97d
BLAKE2b-256 checksum
How to use checksums
bb1db04909f43727917c9445d70066ada20e668e795165f410ed714d1ddd6dbd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.7.20

Release files / stash-0.3.1-cp38-abi3-musllinux_1_1_x86_64.whl

Download URL stash-0.3.1-cp38-abi3-musllinux_1_1_x86_64.whl
Size 521.8 kB
Tags CPython 3.8 Linux musl 1.1+ x86-64 abi3
SHA-256 checksum
How to use checksums
8cd7433853328fba3cd90709790db4c9cf7517a08cf67ed53021918f8699f4e0
BLAKE2b-256 checksum
How to use checksums
ce567d1735b0a11db1cae9481d4102394966bff7a3376633c3330e8d19c16d21
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.7.20

Release files / stash-0.3.1-cp38-abi3-musllinux_1_1_aarch64.whl

Download URL stash-0.3.1-cp38-abi3-musllinux_1_1_aarch64.whl
Size 529.0 kB
Tags CPython 3.8 Linux musl 1.1+ ARM64 abi3
SHA-256 checksum
How to use checksums
73d4f0198c695d21a18216229c3e5bd622c1c4f68ad5066cf9ad00b80c37a58a
BLAKE2b-256 checksum
How to use checksums
9530a6a8083ba95cb4a2648eac9952dbeb7e9927bd684fa5ca12336a991783bd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.7.20

Release files / stash-0.3.1-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL stash-0.3.1-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 352.0 kB
Tags CPython 3.8 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
6742c99b304f7e5a4f1f595b93fcf3b71bc27d932364954b825ec5a267480727
BLAKE2b-256 checksum
How to use checksums
e87dc7c06d4ee598c4d8909e3553f5bc1b8ff15410f65f7589cca1c2d50dbf14
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.7.20

Release files / stash-0.3.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL stash-0.3.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 346.3 kB
Tags CPython 3.8 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
83334bad60dd17a7f9b8bba76296f47a1aa381f85aa1586449320687686d8c42
BLAKE2b-256 checksum
How to use checksums
fbeb884ea1150e1785e46ff95914243ae1ae599b12c03b3254f0113ae46541f5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.7.20

Release files / stash-0.3.1-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl

Download URL stash-0.3.1-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl
Size 626.4 kB
Tags CPython 3.8 abi3 macOS 10.12+ universal2 (ARM64, x86-64) macOS 10.12+ x86-64 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
98a7fb611c3ea68e6735282043c61a518a4e4be1db295c4241fb5571143e1803
BLAKE2b-256 checksum
How to use checksums
e94b6c84fd27bfc1810b681febc6cbeb26f2846166e14896e7f41349446a8fd6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.7.20

Release history Release notifications | RSS feed

This release

0.3.1 This release

8 release files

0.3.0

8 release files

0.2.0

8 release files

0.1.1

8 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page