Python shelve: Simple Persistence

Published on: August 1, 2026
Reading time: 5 minutes
Local database representing persistence with Python shelve

The shelve module stores Python objects in a persistent, dictionary-like file. It is useful when a script needs to keep a small amount of state between runs without installing a database server. The API feels familiar, but the implementation has important consequences for security, portability, concurrency, and long-term maintenance.

When shelve is a good fit

Use shelve for local scripts, prototypes, command-line utilities, small caches, personal automation, and short-lived desktop tools. You open a shelf, assign values to string keys, and close it. For web applications, multiple workers, shared data, complex queries, or critical records, choose SQLite, PostgreSQL, or another transactional database instead.

A basic example

import shelve

with shelve.open("app_data") as db:
    db["user"] = {"name": "Ana", "level": 3}
    db["theme"] = "dark"

with shelve.open("app_data") as db:
    print(db["user"])

The with statement closes the shelf even when an exception occurs. Depending on the operating system and dbm backend, one logical shelf may produce several files with different extensions. Treat the value passed to open as a database base name rather than assuming there will be exactly one file.

How it works internally

shelve combines a key-value database from the dbm family with object serialization provided by pickle. Keys are strings. Values may be lists, dictionaries, tuples, class instances, and other picklable objects. This flexibility is convenient, but the stored data is not human-readable like JSON and must never be loaded from an untrusted source.

Updating mutable values

A common mistake is to retrieve a dictionary or list, mutate it, and expect the change to be saved automatically. The safest pattern is to assign the modified object back to the shelf.

with shelve.open("app_data") as db:
    profile = db["user"]
    profile["level"] += 1
    db["user"] = profile

The writeback=True option caches accessed objects and writes them when the shelf closes. It makes some code shorter, but it can consume substantial memory and make closing unexpectedly slow.

with shelve.open("app_data", writeback=True) as db:
    db["user"]["level"] += 1

Use writeback only for small datasets when its behavior is understood. Explicit reassignment is more predictable and usually easier to review.

Useful dictionary operations

A shelf supports familiar methods such as keys(), values(), items(), get(), and pop(), plus membership testing with in. Full iteration may be expensive because each value can require deserialization.

with shelve.open("app_data") as db:
    db["counter"] = db.get("counter", 0) + 1
    if "settings" in db:
        print(db["settings"])
    for key in db.keys():
        print(key)

Open flags

The flag argument controls how the underlying database is opened. The default c opens for reading and writing and creates the database when needed. r opens an existing database as read-only. w opens an existing database for reading and writing. n creates a new empty database and discards the old one.

Prefer r in code that should only read data, because it avoids accidental creation or modification. Use n only for deliberate rebuilds, isolated tests, or reset commands.

Security rules

Never open a shelf uploaded by a user, downloaded from the internet, received by email, or modified by an untrusted account. Pickle data can execute code while being loaded. Store shelf files in a controlled directory with restrictive permissions and keep them outside public web folders.

shelve does not encrypt values. Passwords, tokens, private keys, and API credentials belong in a secret manager or another protected storage system. File permissions alone may not satisfy the security requirements of production systems.

Concurrency and integrity

shelve does not provide a portable multi-writer locking strategy. Two processes writing to the same shelf can corrupt the database. The safest rule is one writer at a time. If multiple processes or machines must share the data, use SQLite with appropriate transactions or a database service designed for concurrency.

A crash or power loss can also leave files inconsistent. Back up valuable data before migrations, write updates to a temporary database when practical, and test restoration rather than assuming a copied file will always work.

Portability and migrations

The dbm implementation differs across operating systems and Python installations. Files created on one computer may not open on another. Pickled objects also depend on compatible module paths and class definitions. Renaming a class or moving it to another module can make old values impossible to load without migration code.

For records that must survive for years, travel between platforms, or be read by other programming languages, use JSON, CSV, SQLite, or a documented database schema. shelve works best for controlled local persistence.

A small repository wrapper

from pathlib import Path
import shelve

class Repository:
    def __init__(self, path: Path):
        self.path = str(path)

    def save(self, key: str, value) -> None:
        with shelve.open(self.path) as db:
            db[key] = value

    def get(self, key: str, default=None):
        with shelve.open(self.path) as db:
            return db.get(key, default)

    def delete(self, key: str) -> bool:
        with shelve.open(self.path) as db:
            if key not in db:
                return False
            del db[key]
            return True

Wrapping storage behind a repository centralizes paths, validation, logging, and backups. It also makes a future move to SQLite easier because the rest of the application depends on your interface instead of calling shelve everywhere.

Testing safely

Use tempfile.TemporaryDirectory in tests so every test receives isolated storage and the files are removed automatically.

from pathlib import Path
from tempfile import TemporaryDirectory

with TemporaryDirectory() as folder:
    repo = Repository(Path(folder) / "test_db")
    repo.save("x", {"value": 10})
    assert repo.get("x")["value"] == 10

Operational best practices

Use stable, documented keys. Validate values before writing. Always close the database with with. Avoid writeback for large collections. Keep only one writer. Add backups for valuable state. Provide an export command, preferably to JSON, and test that exports can be restored on another machine.

Alternatives

JSON is better for simple portable data. SQLite adds transactions, queries, indexes, and safer concurrent access. dbm stores bytes and leaves serialization to the application. SQLModel and SQLAlchemy are appropriate when the domain and query requirements grow.

Conclusion

shelve is a practical bridge between temporary in-memory dictionaries and a full database. It can remove boilerplate from small local projects, but it should not become an invisible critical dependency. Use it only with trusted files, understand pickle risks, restrict concurrency, and plan a migration when durability or scale becomes important.

Continue with Python zipfile, Python tempfile, Python tomllib, and Python copy. See the official shelve documentation and the pickle security warning.

Before adopting the module, document what will be stored, how long it must remain available, and how recovery will work. This small architectural decision prevents a prototype from becoming an unmonitored production dependency.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Command terminal representing safe parsing with Python shlex
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Python shlex: Parse Commands Safely

    Learn Python shlex to split command lines, handle quotes, use quote and join, build mini-languages, and reduce shell injection risks.

    Ler mais

    Tempo de leitura: 6 minutos
    02/08/2026
    Text documents representing version comparison with Python difflib
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Python difflib: Compare Text and Files

    Learn Python difflib to compare text, measure similarity, create unified diffs, generate HTML reports, and suggest close names.

    Ler mais

    Tempo de leitura: 6 minutos
    01/08/2026
    Chart dashboard representing statistical data analysis in Python
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Python statistics: Data Analysis Guide

    Learn Python statistics for mean, median, standard deviation, quantiles, correlation, regression, NormalDist, and KDE.

    Ler mais

    Tempo de leitura: 6 minutos
    31/07/2026
    Fraction charts representing exact rational numbers in Python
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Python fractions: Exact Rational Numbers

    Learn Python fractions for exact rational arithmetic, automatic reduction, limit_denominator, formatting, and safe conversions.

    Ler mais

    Tempo de leitura: 5 minutos
    31/07/2026
    Calculator and documents representing precise Decimal calculations in Python
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Python Decimal: Precise Calculations

    Learn Python Decimal for exact calculations, money, quantize, rounding modes, contexts, and validation without float errors.

    Ler mais

    Tempo de leitura: 6 minutos
    30/07/2026
    Digital code representing unique and sortable UUID identifiers in Python
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Python uuid: Unique and Sortable IDs

    Learn Python uuid versions 4, 5, 6, and 7, validation, database storage, sortable IDs, and essential security practices.

    Ler mais

    Tempo de leitura: 7 minutos
    30/07/2026