尚未翻译
本页面尚未翻译成中文,因此显示英文原文。 帮助翻译
shelve Module Complexity¶
The shelve module is a dictionary whose values persist: every value is pickled on the way in
and unpickled on the way out, and the bytes live in a dbm database on disk. It is a thin layer,
so almost every operation is one call to that database plus one pickle or unpickle; the shelf
keeps no values of its own unless writeback=True asks for a cache.
n is the number of keys in the shelf, v is the pickled length of the value an operation
stores or reads (the largest, where it handles several), V is the pickled length of all the
values it reads or stores, and w is the pickled length of everything in the writeback cache.
Pickling and unpickling are priced as linear in the pickle, and keys are short strings, so
encoding one is O(1). The bounds assume a dbm database underneath, as shelve.open() gives,
and are shelve's own work: each row names the backend calls it makes, and what one lookup, store
or delete costs belongs to the backend (see dbm). With writeback=True, every value
read or stored is also kept in the cache, which the Space column leaves to the Shelf.cache row,
and reading a cached key again costs O(1).
Complexity Reference¶
Opening a shelf¶
| Operation | Time | Space | Notes |
|---|---|---|---|
shelve.open(filename, flag='c', protocol=None, writeback=False) |
O(1) | O(1) | Plus opening the backend dbm.open() picks: an existing file keeps its backend, a new one gets the first available, dbm.sqlite3 from 3.13. flag='n' always creates a new, empty database with that default |
shelve.DbfilenameShelf(filename, flag='c', protocol=None, writeback=False) |
O(1) | O(1) | What shelve.open() returns |
shelve.Shelf(dict, protocol=None, writeback=False, keyencoding='utf-8') |
O(1) | O(1) | Wraps any mapping with bytes keys, such as a database from a chosen dbm backend |
shelve.BsdDbShelf(dict, protocol=None, writeback=False, keyencoding='utf-8') |
O(1) | O(1) | A Shelf over an object with cursor methods; no standard-library dbm backend has them |
Shelf¶
| Operation | Time | Space | Notes |
|---|---|---|---|
shelf[key] |
O(v) | O(v) | One backend lookup, then the value is unpickled again on every read, so a mutable value comes back as a new copy. With writeback=True a key already read or stored is a cache hit: O(1), the same object |
shelf[key] = value |
O(v) | O(v) | Pickles the value and stores it at once, with or without writeback |
del shelf[key] |
O(1) | O(1) | One backend delete |
key in shelf |
O(1) | O(1) | One backend lookup; nothing is unpickled |
Shelf.get(key, default=None) |
O(v) | O(v) | A membership test, then a read if the key is present |
len(shelf) |
O(1) | O(1) | One backend len(); dbm.ndbm counts its keys again after every write, O(n) |
Iterating a shelf, list(shelf), iterating shelf.keys() |
O(n) | O(n) | The backend's whole key list is built before the first key comes back. shelf.keys() itself is an O(1) view |
Iterating shelf.items() or shelf.values() |
O(n + V) | O(n + v) | The key list, then one lookup and one unpickle per key |
shelf.pop(key[, default]), shelf.setdefault(key, default=None) |
O(v) | O(v) | A read and a delete, or a read that falls back to a store |
shelf.popitem() |
O(n + v) | O(n + v) | Builds the whole key list to find one key |
shelf.update(other) |
O(V) | O(v) | One store per item of other |
Shelf.clear() |
O(n² + V) | O(n + v) | One popitem() per key, so each removal rebuilds the key list and unpickles the value it drops |
DbfilenameShelf.clear() |
One backend clear() from Python 3.13, O(n² + V) before |
O(1) from Python 3.13, O(n + v) before | The backend's clear() still removes keys one at a time. Before 3.13 this is Shelf.clear() |
Shelf.sync() |
O(w) | O(v) | Re-pickles and stores every cached entry, read or written, then empties the cache and calls the backend's sync() if it has one. O(1) without writeback |
Shelf.close() |
O(w) | O(v) | sync(), then the backend's close(); later operations raise ValueError. Also what leaving a with block does |
Shelf.cache |
O(1) | O(w) | With writeback=True, every key read or stored stays in it until it is deleted or the shelf is synced, cleared or closed; empty otherwise |
BsdDbShelf¶
| Operation | Time | Space | Notes |
|---|---|---|---|
BsdDbShelf.first(), BsdDbShelf.last(), BsdDbShelf.next(), BsdDbShelf.previous() |
O(v) | O(v) | One cursor step on the wrapped object, then an unpickled (key, value) pair |
BsdDbShelf.set_location(key) |
O(v) | O(v) | Positions the cursor at key and returns its pair |
Reading and Writing¶
A store pickles the value and hands the bytes straight to the backend, so the shelf holds no pending writes of its own. A read unpickles the value again every time: changing what it returns changes nothing on disk until it is stored again.
import os
import shelve
import tempfile
with tempfile.TemporaryDirectory() as directory:
path = os.path.join(directory, 'data')
with shelve.open(path) as shelf: # O(1) plus the backend's open
shelf['name'] = 'Alice' # O(v) - pickled and stored now
shelf['scores'] = [95, 87, 92]
assert 'name' in shelf # O(1) - nothing unpickled
assert shelf.get('email', 'none') == 'none'
scores = shelf['scores'] # O(v) - a new copy
assert scores is not shelf['scores']
shelf['scores'].append(100) # changes a copy that is thrown away
assert shelf['scores'] == [95, 87, 92]
scores.append(100)
shelf['scores'] = scores # O(v) - store it again to keep it
with shelve.open(path, flag='r') as shelf:
assert shelf['scores'] == [95, 87, 92, 100]
assert len(shelf) == 2 # one backend len()
shelf.close() # closing twice is harmless
try:
shelf['name']
except ValueError as error:
assert 'closed shelf' in str(error)
else:
raise AssertionError('a closed shelf was read')
Writeback¶
writeback=True keeps every value it reads or stores in memory, so in-place changes stick. The
price is that memory grows with every key touched, and sync() or close() re-pickles all of
them, changed or not, because the shelf cannot tell which ones were changed.
import os
import shelve
import tempfile
with tempfile.TemporaryDirectory() as directory:
path = os.path.join(directory, 'data')
with shelve.open(path, writeback=True) as shelf:
shelf['log'] = [] # O(v) - stored now and cached
shelf['log'].append('started') # O(1) - a cache hit, the same object
assert shelf['log'] is shelf['log']
assert len(shelf.cache) == 1
shelf.sync() # O(w) - re-pickles every cached entry
assert shelf.cache == {}
with shelve.open(path) as shelf:
assert shelf['log'] == ['started']
Iterating a Shelf¶
Iteration asks the backend for all of its keys at once. Every dbm backend returns them as a
list, so even taking the first key costs O(n) time and memory, and popitem() costs O(n + v).
items() and values() add one lookup and one unpickle per key.
import os
import shelve
import tempfile
with tempfile.TemporaryDirectory() as directory:
with shelve.open(os.path.join(directory, 'data')) as shelf:
shelf.update({'a': [1, 2], 'b': [3], 'c': []}) # O(V) - one store each
assert set(shelf) == {'a', 'b', 'c'} # O(n) - one key list
keys = shelf.keys() # O(1) - a view; iterating it is O(n)
assert len(keys) == 3
total = sum(len(value) for value in shelf.values()) # O(n + V)
assert total == 3
key, value = shelf.popitem() # O(n + v) - the key list to find one key
assert key not in shelf and len(shelf) == 2
Choosing a Backend¶
shelve.open() lets dbm.open() choose: an existing file is opened with the backend that wrote
it unless flag='n' asks for a new one, and a new one gets the first available of dbm.sqlite3
(3.13+), dbm.gnu, dbm.ndbm and dbm.dumb. To choose yourself, open the database with that
backend and wrap it in Shelf.
import dbm
import dbm.dumb
import os
import shelve
import tempfile
with tempfile.TemporaryDirectory() as directory:
path = os.path.join(directory, 'data')
with shelve.Shelf(dbm.dumb.open(path, 'c')) as shelf: # O(1) - wraps it
shelf['answer'] = 42
assert dbm.whichdb(path) == 'dbm.dumb'
with shelve.open(path) as shelf: # the existing backend is kept
assert shelf['answer'] == 42
Emptying a Shelf¶
clear() removes one key at a time. On a Shelf, and on shelve.open()'s shelf before 3.13,
each removal is a popitem(), which rebuilds the whole key list and unpickles the value it
drops: O(n² + V) for the shelf. From 3.13 shelve.open()'s shelf hands the job to the backend's
clear() instead. Opening the file again with flag='n' starts from an empty database without
touching the keys at all.
import os
import shelve
import tempfile
with tempfile.TemporaryDirectory() as directory:
path = os.path.join(directory, 'data')
with shelve.open(path) as shelf:
shelf.update((f'key{i}', i) for i in range(100))
shelf.clear() # key by key
assert len(shelf) == 0
shelf.update((f'key{i}', i) for i in range(100))
with shelve.open(path, flag='n') as shelf: # a new, empty database
assert len(shelf) == 0
Common Patterns¶
A Persistent Memo¶
import os
import shelve
import tempfile
def slow_square(x):
return x * x
with tempfile.TemporaryDirectory() as directory:
path = os.path.join(directory, 'memo')
computed = []
def cached_square(shelf, x):
key = str(x)
if key in shelf: # O(1) - no unpickling to check
return shelf[key] # O(v)
computed.append(x)
shelf[key] = result = slow_square(x) # O(v)
return result
with shelve.open(path) as shelf:
assert cached_square(shelf, 12) == 144
with shelve.open(path) as shelf: # a later run finds it on disk
assert cached_square(shelf, 12) == 144
assert computed == [12]
Performance Best Practices¶
✅ Do:
- Use
key in shelfwhen only existence matters: it unpickles nothing, whereshelf[key]unpickles the whole value - Read a mutable value once, change it, and store it once, rather than reaching for
writeback - Keep
writeback=Trueshelves short-lived, orsync()them, since every key touched stays in memory and is re-pickled - Reopen a
shelve.open()shelf withflag='n'to empty it, instead of callingclear(), which removes keys one at a time
❌ Avoid:
for key in shelforpopitem()just to take one key - both build the whole key listlen(shelf)in a loop that also writes, on a backend that counts its keys- Mutating
shelf[key]in place withoutwriteback- the change is made to a copy and lost
Version Notes¶
- Python 3.13+: New shelves use the
dbm.sqlite3backend whensqlite3is available; before, the first available ofdbm.gnu,dbm.ndbmanddbm.dumb - Python 3.13+:
DbfilenameShelf.clear()calls the backend'sclear(); before, it removed keys throughpopitem()at O(n² + V) - All Python 3: Reading a key returns a copy; an in-place change is lost unless it is stored
again or the shelf uses
writeback=True