shelve: Without writeback=True, a Mutation Vanishes in the Same Session (tested)

shelve looks like a dict that happens to persist to disk, so shelf["x"].append(4) looks like it should behave exactly like d["x"].append(4) on a plain dict. It doesn't, by default: every shelf[key] read unpickles a brand-new object from disk. We measured what that does to an in-place mutation, including whether it's even visible before you close or reopen anything.

1. The mutation looks like it worked -- and then it's just gone

import shelve, tempfile, os

path = os.path.join(tempfile.mkdtemp(), "db")
s = shelve.open(path)   # writeback=False is the default
s["x"] = [1, 2, 3]
s["x"].append(4)        # no error -- looks exactly like it worked
print(s["x"])
[1, 2, 3]

Measured: s["x"].append(4) raised nothing and returned nothing unusual -- a list's .append() always "succeeds" syntactically. But reading s["x"] back on the very next line, in the same process, before calling .close() or touching the file again, shows [1, 2, 3]. The 4 never existed anywhere except on a temporary object that was discarded the instant the statement finished.

2. Why: every read returns a different object, not a cached one

import shelve, tempfile, os

path = os.path.join(tempfile.mkdtemp(), "db")
s = shelve.open(path)
s["x"] = [1, 2, 3]

a = s["x"]
b = s["x"]
print("same object?", a is b)
same object? False

Measured: two reads of the same key, back to back, are not the same object -- each shelf[key] unpickles a fresh copy from the underlying dbm file. s["x"].append(4) mutates one of those disposable copies, which is never stored anywhere and is garbage the moment the line finishes. This is unlike a plain dict, where d["x"] always returns the one object actually being stored.

3. writeback=True fixes it -- by caching every object you've touched, in memory

import shelve, tempfile, os

path = os.path.join(tempfile.mkdtemp(), "db")
s = shelve.open(path, writeback=True)
s["x"] = [1, 2, 3]
s["x"].append(4)
print("writeback=True, same session:", s["x"])
s.close()

s2 = shelve.open(path)
print("reopened:", s2["x"])
s2.close()
writeback=True, same session: [1, 2, 3, 4]
reopened: [1, 2, 3, 4]

Measured: with writeback=True, the shelf keeps an in-memory cache of every entry you've accessed and writes it all back on close() -- so the mutation is visible immediately, and persists. The documented tradeoff is memory: that cache holds every object you've touched, uncollected, for the life of the shelf, and nothing is written to disk until close() (or an explicit sync()) runs. Without it, the only safe pattern is read-modify-store: tmp = s["x"]; tmp.append(4); s["x"] = tmp, which we also measured to work correctly in the same session.

Frequently asked questions

Does shelve raise an error if you mutate a stored list without writeback=True?

No -- we measured calling .append() on a value read from a shelf with the default writeback=False: it raised nothing, and the mutation was already gone by the next line, even in the same process before any close() or reopen().

Why does shelve lose in-place mutations by default?

We measured that shelf[key] returns a newly-unpickled object on every single read when writeback is off -- two back-to-back reads of the same key are provably different objects (a is b is False) -- so mutating one of them has nothing to persist.

Does writeback=True fix the mutation problem?

Yes -- we measured that with writeback=True, the same append() call is visible immediately in the same session and still present after closing and reopening the shelf, because shelve caches every touched key in memory and writes it all back on close().

What's the safe way to mutate a value in a shelve database without writeback=True?

Read it into a local variable, mutate the local variable, then explicitly assign it back to the key -- we measured that tmp = s["x"]; tmp.append(4); s["x"] = tmp correctly persists the change in the same session.

Written with AI assistance. Measured on 2026-10-08 with Python 3.13.5's built-in shelve module (default dbm backend): opened a shelf without writeback, mutated a stored list in place, and read the key back in the exact same session before closing anything. PlainHeaders is our extension; Python's shelve module is built in.