The shelve module provides a simple way to persist Python objects. It's like a dictionary that automatically saves to disk—perfect for quick local storage.

Basic Usage

Store and retrieve objects:

import shelve
 
# Open a shelf (creates files like data.db)
with shelve.open('mydata') as db:
    db['name'] = 'Alice'
    db['scores'] = [95, 87, 92]
    db['config'] = {'debug': True, 'version': 1}
 
# Data persists across runs
with shelve.open('mydata') as db:
    print(db['name'])    # Alice
    print(db['scores'])  # [95, 87, 92]

Dictionary Interface

Shelves behave like dictionaries:

import shelve
 
with shelve.open('mydata') as db:
    # Set values
    db['key'] = 'value'
    
    # Get values
    value = db['key']
    value = db.get('missing', 'default')
    
    # Check existence
    if 'key' in db:
        print('exists')
    
    # Delete
    del db['key']
    
    # Iterate
    for key in db:
        print(key, db[key])
    
    # Get all keys
    print(list(db.keys()))

Storing Complex Objects

Any picklable Python object works:

import shelve
from datetime import datetime
 
class User:
    def __init__(self, name, email):
        self.name = name
        self.email = email
        self.created = datetime.now()
 
with shelve.open('users') as db:
    db['alice'] = User('Alice', 'alice@example.com')
    db['bob'] = User('Bob', 'bob@example.com')
 
# Later...
with shelve.open('users') as db:
    user = db['alice']
    print(f"{user.name}: {user.email}")

Writeback Mode

By default, modifying retrieved objects doesn't persist:

import shelve
 
# Without writeback - changes are LOST
with shelve.open('mydata') as db:
    db['items'] = [1, 2, 3]
 
with shelve.open('mydata') as db:
    db['items'].append(4)  # This doesn't persist!
 
with shelve.open('mydata') as db:
    print(db['items'])  # [1, 2, 3] - append was lost
 
# With writeback - changes persist
with shelve.open('mydata', writeback=True) as db:
    db['items'].append(4)  # This persists!
 
with shelve.open('mydata') as db:
    print(db['items'])  # [1, 2, 3, 4]

Writeback Trade-offs

import shelve
 
# writeback=True:
# + Modifications to objects persist
# + More intuitive behavior
# - Uses more memory (caches all accessed items)
# - Slower close() (writes all cached items)
 
with shelve.open('mydata', writeback=True) as db:
    # All accessed items cached until close
    for key in db:
        db[key]['modified'] = True  # Works!

Manual Sync

Force write to disk:

import shelve
 
db = shelve.open('mydata', writeback=True)
db['key'] = 'value'
db.sync()  # Write to disk now
# ... more operations ...
db.close()

Read-Only Mode

Open for reading only:

import shelve
 
with shelve.open('mydata', flag='r') as db:
    print(db['key'])
    # db['new'] = 'value'  # Error! Read-only

Flags

import shelve
 
# 'c' - Create if doesn't exist (default)
# 'n' - Always create new (truncate existing)
# 'r' - Read-only
# 'w' - Read-write (error if doesn't exist)
 
# Fresh start
with shelve.open('mydata', flag='n') as db:
    db['fresh'] = 'data'

Simple Cache

import shelve
import hashlib
import time
 
def cached_computation(key, compute_func, ttl=3600):
    """Cache computation results to disk."""
    cache_key = hashlib.md5(key.encode()).hexdigest()
    
    with shelve.open('cache') as cache:
        if cache_key in cache:
            entry = cache[cache_key]
            if time.time() - entry['time'] < ttl:
                return entry['value']
        
        # Compute and cache
        value = compute_func()
        cache[cache_key] = {'value': value, 'time': time.time()}
        return value

Session Storage

import shelve
import uuid
 
class SessionStore:
    def __init__(self, path='sessions'):
        self.path = path
    
    def create(self, data):
        """Create new session."""
        session_id = str(uuid.uuid4())
        with shelve.open(self.path) as db:
            db[session_id] = data
        return session_id
    
    def get(self, session_id):
        """Get session data."""
        with shelve.open(self.path) as db:
            return db.get(session_id)
    
    def update(self, session_id, data):
        """Update session."""
        with shelve.open(self.path) as db:
            db[session_id] = data
    
    def delete(self, session_id):
        """Delete session."""
        with shelve.open(self.path) as db:
            if session_id in db:
                del db[session_id]

Application Config

import shelve
 
class Config:
    def __init__(self, path='config'):
        self.path = path
        self._defaults = {
            'debug': False,
            'log_level': 'INFO',
            'max_connections': 10
        }
    
    def get(self, key):
        with shelve.open(self.path) as db:
            return db.get(key, self._defaults.get(key))
    
    def set(self, key, value):
        with shelve.open(self.path) as db:
            db[key] = value
    
    def reset(self):
        with shelve.open(self.path, flag='n') as db:
            for key, value in self._defaults.items():
                db[key] = value

File Location

Shelve creates database files:

import shelve
import os
 
with shelve.open('mydata') as db:
    db['test'] = 'data'
 
# Creates files like:
# mydata.db (or mydata.dir, mydata.bak, mydata.dat)
# Exact files depend on underlying dbm implementation

Limitations

  1. Keys must be strings: Unlike dict, shelve requires string keys
  2. Not thread-safe: Use locking for concurrent access
  3. Not suitable for large data: Better to use SQLite or dedicated DB
  4. Pickling required: Objects must be picklable
import shelve
 
with shelve.open('mydata') as db:
    # db[123] = 'value'  # Error! Key must be string
    db['123'] = 'value'  # OK

Thread Safety

Add locking for concurrent access:

import shelve
import threading
 
class ThreadSafeShelf:
    def __init__(self, path):
        self.path = path
        self.lock = threading.Lock()
    
    def get(self, key):
        with self.lock:
            with shelve.open(self.path) as db:
                return db.get(key)
    
    def set(self, key, value):
        with self.lock:
            with shelve.open(self.path) as db:
                db[key] = value

When to Use shelve

Use shelve when:

  • Quick prototype persistence
  • Simple key-value storage
  • Caching Python objects locally
  • Small to medium data sizes

Use alternatives when:

  • Need concurrent access → SQLite
  • Large datasets → SQLite, Redis
  • Need queries → SQLite
  • Cross-language compatibility → JSON, SQLite

Shelve is the quickest path from "I need to save this" to working code.

React to this post: