Serialize Objects with Python `pickle`

Learn how to use Python's pickle module to serialize and deserialize Python objects for storage and transfer.

6 min read

The pickle module in the Python standard library serializes Python objects into a byte stream and restores them later. Unlike JSON, which only handles basic types, pickle can save and restore almost any Python object: custom classes, nested data structures, functions, and even some compiled code objects.

pythonpython
import pickle
 
data = {"name": "Maya", "scores": [92, 85, 78], "active": True}
serialized = pickle.dumps(data)
restored = pickle.loads(serialized)
 
print(restored)              # {'name': 'Maya', 'scores': [92, 85, 78], 'active': True}
print(restored == data)  # True

pickle.dumps(data) converts the dictionary to bytes. pickle.loads(serialized) reconstructs the original object. The restored object is equal to the original.

Writing pickled objects to a file

Serializing to a string is useful in memory, but most of the time you want the result saved somewhere durable. Use pickle.dump() to serialize an object directly to a binary file instead of building bytes yourself.

pythonpython
import pickle
 
user = {"id": 42, "name": "Maya", "settings": {"theme": "dark", "notifications": True}}
 
with open("user.pkl", "wb") as file:
    pickle.dump(user, file)

The file must be opened in binary write mode ("wb"). Pickle produces bytes, not text. After this code runs, user.pkl contains the serialized dictionary.

Reading pickled objects from a file

Use pickle.load() to deserialize an object from a binary file.

pythonpython
import pickle
 
with open("user.pkl", "rb") as file:
    user = pickle.load(file)
 
print(user["name"])              # Maya
print(user["settings"]["theme"])  # dark

Open the file in binary read mode ("rb"). pickle.load(file) reads the bytes and reconstructs the exact Python object that was saved.

Pickling custom objects

Pickle can serialize instances of classes you define, as long as the class itself can be imported wherever the data gets loaded back.

pythonpython
class Player:
    def __init__(self, name, level, inventory):
        self.name = name
        self.level = level
        self.inventory = inventory
 
    def __repr__(self):
        return f"Player({self.name}, level={self.level})"
 
player = Player("Maya", 14, ["sword", "shield", "potion"])

With the Player instance created, save it to a file and load it back the same way you would any other object:

pythonpython
import pickle
 
with open("savegame.pkl", "wb") as file:
    pickle.dump(player, file)
 
with open("savegame.pkl", "rb") as file:
    loaded = pickle.load(file)
 
print(loaded)             # Player(Maya, level=14)
print(loaded.inventory)  # ['sword', 'shield', 'potion']

Pickle saves the object's dict (its instance attributes) by default. When you load it, Python reconstructs a Player object with the same attributes. The class definition must be importable in the loading script, or pickle will raise an AttributeError.

Pickle protocols

Pickle supports several protocol versions. Higher protocols are more efficient and support more Python features.

pythonpython
import pickle
 
data = list(range(1000))
 
for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
    size = len(pickle.dumps(data, protocol=protocol))
    print(f"Protocol {protocol}: {size} bytes")

Running this loop over a list of 1000 integers shows how much the output size changes across protocol versions, since each protocol encodes the same data with a different level of compactness:

texttext
Protocol 0: 5896 bytes
Protocol 1: 2750 bytes
Protocol 2: 2752 bytes
Protocol 3: 2752 bytes
Protocol 4: 2760 bytes
Protocol 5: 2760 bytes

Protocol 0 is the original text-based protocol and produces the largest output by far. Protocol 1 and later use a compact binary format, so the size drops sharply and then stays roughly flat as newer protocols add small framing improvements rather than smaller output.

Always use pickle.HIGHEST_PROTOCOL for new code, since it supports the most Python features. Only use a lower protocol if you need compatibility with older Python versions.

pythonpython
pickle.dump(data, file, protocol=pickle.HIGHEST_PROTOCOL)

What pickle can and cannot serialize

Pickle handles most Python objects, but there are important exceptions.

Pickle can serialize:

  • Built-in types: None, True, False, integers, floats, complex, strings, bytes, bytearrays
  • Collections: tuples, lists, sets, dictionaries
  • Instances of user-defined classes
  • Functions and classes defined at the top level of a module

Pickle cannot serialize:

  • File objects, sockets, and database connections
  • Generators and iterators with internal state
  • Lambda functions (in most cases)
  • Objects that depend on external system state

If you try to pickle an unpicklable object, you get a TypeError:

pythonpython
import pickle
 
pickle.dumps(open("test.txt", "w"))

An open file handle wraps operating system resources that have no meaningful representation as bytes, so pickle refuses the object outright instead of producing broken or misleading output:

texttext
TypeError: cannot pickle 'TextIOWrapper' instances

Security warning

The most important rule with pickle: never unpickle data from an untrusted source. Pickle can execute arbitrary code during deserialization. An attacker can craft a malicious pickle that runs system commands, deletes files, or exfiltrates data when you call pickle.load.

pythonpython
import pickle
 
# Dangerous: data from an untrusted API, user upload, or network
# data = request.body
# obj = pickle.loads(data)  # DO NOT DO THIS

Pickle is safe for:

  • Saving program state to a local file
  • Caching computation results for later use
  • Sending data between trusted components of the same application
  • Checkpointing during long-running tasks

If you need to accept serialized data from external sources, use JSON, CSV files, or a well-defined schema format instead.

Practical example: caching expensive computation

A common safe use of pickle is caching results so you do not repeat expensive work. Load an existing cache from disk if one is already there, and start with an empty dictionary on the first run:

pythonpython
import pickle
from pathlib import Path
 
cache_path = Path("fibonacci_cache.pkl")
 
if cache_path.exists():
    with open(cache_path, "rb") as file:
        cache = pickle.load(file)
else:
    cache = {}

The fibonacci function checks the cache before doing any recursive work, and every new result gets stored back into the cache dictionary as it is computed:

pythonpython
def fibonacci(n):
    if n in cache:
        return cache[n]
    if n <= 1:
        result = n
    else:
        result = fibonacci(n - 1) + fibonacci(n - 2)
    cache[n] = result
    return result

Calling the function once populates the cache, and saving it back to disk means the next run of the script can skip straight to the answer instead of recomputing every value:

pythonpython
print(fibonacci(35))
 
with open(cache_path, "wb") as file:
    pickle.dump(cache, file, protocol=pickle.HIGHEST_PROTOCOL)

The first run computes Fibonacci numbers and saves the cache. Subsequent runs load the cache and skip the repeated computation. The cache file is trusted because it was created by the same program.

Pickle vs JSON vs CSV

FeaturepickleJSONCSV
Serializes custom classesYesNoNo
Human-readableNoYesYes
Cross-languageNoYesYes
Safe for untrusted inputNoYesYes
Output formatBytesTextText
Preserves exact typesYesNo (loose mapping)No (all strings)

Choose pickle when you need to preserve exact Python object state in trusted environments. Choose JSON for APIs, configuration, and cross-language data exchange. Choose CSV for tabular data meant for spreadsheets or data analysis tools.

Common mistakes

Forgetting binary mode. Opening a file with "w" instead of "wb" causes a TypeError because pickle produces bytes, not a string. Always use "wb" for writing and "rb" for reading.

Unpickling untrusted data. This is the most serious pickle mistake. Only unpickle data you created yourself or received from a fully trusted source.

Relying on pickle for long-term storage. Pickle format can change across Python versions. For data you need to read years later, use JSON, CSV, or a database.

Rune AI

Rune AI

Key Insights

  • Pickle serializes Python objects to bytes and restores them with full fidelity.
  • Use pickle.dump(obj, file) to write and pickle.load(file) to read.
  • Use pickle.dumps(obj) and pickle.loads(data) for in-memory bytes.
  • Never unpickle data from untrusted sources; pickle can execute arbitrary code.
  • Choose the highest protocol version with protocol=pickle.HIGHEST_PROTOCOL for best performance.
  • For cross-language or human-readable needs, prefer JSON or CSV over pickle.
RunePowered by Rune AI

Frequently Asked Questions

Is pickle safe to use?

Only unpickle data from sources you trust. Pickle can execute arbitrary code during deserialization, so never unpickle data received from an untrusted network, API, or user upload. For trusted local storage like caching or checkpointing, pickle is safe.

What is the difference between pickle and JSON?

Pickle can serialize almost any Python object including custom classes, functions, and complex nested structures. JSON only supports dicts, lists, strings, numbers, booleans, and null. Use JSON when you need human-readable output or cross-language compatibility. Use pickle when you need to preserve exact Python object state.

Can I share pickled data across Python versions?

Pickle is generally compatible across Python 3 versions when using the same or lower protocol. Avoid sharing pickled data across major Python versions (2 vs 3) unless you use protocol version 2 or lower. For long-term storage, consider a format-independent serialization like JSON or a database.

Conclusion

Pickle is Python's built-in serialization tool for saving and restoring Python objects. Use it for caching, checkpointing, and trusted local storage. Never unpickle data from untrusted sources. When you need cross-language compatibility or human-readable output, use JSON or CSV instead.