python 52 lines · 7 steps

Copy-on-write attribute snapshots in Python

A wrapper that layers edits over an untouched base object, deferring real mutation until you commit.

Explained by highlit
1from copy import deepcopy
2 
3 
4class CowSnapshot:
5 __slots__ = ("_base", "_overrides", "_deleted")
6 
7 def __init__(self, base):
8 object.__setattr__(self, "_base", base)
9 object.__setattr__(self, "_overrides", {})
10 object.__setattr__(self, "_deleted", set())
11 
12 def __getattr__(self, name):
13 overrides = object.__getattribute__(self, "_overrides")
14 if name in overrides:
15 return overrides[name]
16 if name in object.__getattribute__(self, "_deleted"):
17 raise AttributeError(name)
18 return getattr(object.__getattribute__(self, "_base"), name)
19 
20 def __setattr__(self, name, value):
21 object.__getattribute__(self, "_deleted").discard(name)
22 object.__getattribute__(self, "_overrides")[name] = value
23 
24 def __delattr__(self, name):
25 overrides = object.__getattribute__(self, "_overrides")
26 overrides.pop(name, None)
27 object.__getattribute__(self, "_deleted").add(name)
28 
29 def is_dirty(self):
30 return bool(object.__getattribute__(self, "_overrides")) or bool(
31 object.__getattribute__(self, "_deleted")
32 )
33 
34 def commit(self):
35 base = object.__getattribute__(self, "_base")
36 for name, value in object.__getattribute__(self, "_overrides").items():
37 setattr(base, name, value)
38 for name in object.__getattribute__(self, "_deleted"):
39 if hasattr(base, name):
40 delattr(base, name)
41 object.__getattribute__(self, "_overrides").clear()
42 object.__getattribute__(self, "_deleted").clear()
43 return base
44 
45 def materialize(self):
46 snapshot = deepcopy(object.__getattribute__(self, "_base"))
47 for name, value in object.__getattribute__(self, "_overrides").items():
48 setattr(snapshot, name, deepcopy(value))
49 for name in object.__getattribute__(self, "_deleted"):
50 if hasattr(snapshot, name):
51 delattr(snapshot, name)
52 return snapshot
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Overriding the attribute dunders lets you intercept every read, write, and delete to build a transparent proxy.
  2. 2A tombstone set distinguishes 'not set here' from 'explicitly deleted', which a plain overrides dict can't express alone.
  3. 3Deferring mutation to an explicit commit gives you cheap, discardable edits over shared state.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Copy-on-write attribute snapshots in Python — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code