Serialization with pickle
Reviewed & published by Brayan K
pickle is Python's built-in serializer: it converts almost any Python object into a stream of bytes you can save or send, and reconstructs the exact object — types and all — when you read those bytes back.
Part of the free Python course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
Pickle is wonderfully convenient for saving program state and caching results between runs — but it comes with one serious safety rule that you must never forget. We'll cover the power and the danger together.
What You'll Learn in This Lesson
- Serializing to bytes with pickle.dumps / loads
- Saving to and loading from files with dump / load
- Pickling custom class instances
- What can and can't be pickled
- Protocol versions and why they matter
- The critical security rule — and pickle vs JSON
1 Serializing to Bytes with dumps / loads
pickle.dumps(obj) turns an object into a bytes blob; pickle.loads(blob) turns it back. The reconstructed object is a full, independent copy with the original types intact:
import pickle
data = {"name": "Ada", "scores": [90, 85, 88], "active": True}
blob = pickle.dumps(data) # object -> bytes
print("Type:", type(blob).__name__) # bytes
restored = pickle.loads(blob) # bytes -> object
print("Restored:", restored)
print("Equal:", restored == data) # True
print("Same object:", restored is data) # False (a fresh copy)object --dumps--> bytes --loads--> identical object
(store/send)Remember: the "s" in dumps / loads means "string of bytes" — these stay in memory. The file versions drop the "s".
💾 Writing to Files with dump / load
pickle.dump(obj, file) writes the bytes straight to a binary file; pickle.load(file) reads them back. In a real program the file is opened with open("data.pkl", "wb"); here we use an in-memory BytesIO buffer so it runs anywhere:
import pickle
from io import BytesIO
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Point({self.x}, {self.y})"
buffer = BytesIO() # stands in for a real file
pickle.dump(Point(3, 4), buffer) # write bytes into it
buffer.seek(0) # rewind to the start to read
loaded = pickle.load(buffer) # read the object back
print("Loaded:", loaded) # Point(3, 4)
print("x + y =", loaded.x + loaded.y) # 7On disk you'd write with open("point.pkl", "wb") as f: pickle.dump(obj, f) and read with "rb". The mode must be binary ("wb" / "rb") because pickle produces bytes, not text.
2 Pickling Custom Objects (and What Can't Be Pickled)
Most built-in and custom objects pickle cleanly — including instances of your own classes. But a few things cannot be pickled: lambdas, open file handles, sockets, and (by default) locally-defined functions:
import pickle
# Custom class instances pickle fine
class Account:
def __init__(self, owner, balance):
self.owner = owner
self.balance = balance
acct = Account("Ada", 500)
copy = pickle.loads(pickle.dumps(acct))
print(copy.owner, copy.balance) # Ada 500
# A lambda cannot be pickled
try:
pickle.dumps(lambda x: x * 2)
except pickle.PicklingError as e:
print("Cannot pickle lambda:", type(e).__name__)
# Cannot pickle lambda: PicklingErrorimport pickle
from io import BytesIO
class Task:
def __init__(self, title, done=False):
self.title = title
self.done = done
def __repr__(self):
mark = "x" if self.done else " "
return f"[{mark}] {self.title}"
# A whole to-do list of custom objects
todo = [Task("Write tests", True), Task("Ship feature"), Task("Celebrate")]
# Save it to an in-memory binary buffer
buffer = BytesIO()
pickle.dump(todo, buffer)
# ...later, load it back
buffer.seek(0)
restored = pickle.load(buffer)
for task in restored:
print(task)
print("Count:", len(restored))
# ✅ Expected output:
# [x] Write tests
# [ ] Ship feature
# [ ] Celebrate
# Count: 33 Protocol Versions
Pickle has several binary protocols, numbered 0 through 5. Higher protocols are more efficient and support more types, but older Python versions can't read them. You can pick one explicitly:
import pickle
print("Highest available:", pickle.HIGHEST_PROTOCOL) # 5
print("Default used: ", pickle.DEFAULT_PROTOCOL) # 4
data = {"x": 1, "y": [2, 3]}
# Choose a protocol explicitly via the 'protocol' argument
v5 = pickle.dumps(data, protocol=5) # fastest, newest
v0 = pickle.dumps(data, protocol=0) # oldest, human-ish, biggest
print("Protocol 5 smaller than 0:", len(v5) < len(v0)) # True
print("Both round-trip equally: ",
pickle.loads(v5) == pickle.loads(v0) == data) # True⚖️ pickle vs JSON
JSON and pickle solve overlapping problems with very different trade-offs. Pickle preserves exact Python types but is unsafe and Python-only; JSON is text, language-neutral, and safe — but loses some types:
import pickle, json
data = {"id": 1, "tags": ("a", "b"), "nums": {3, 1, 2}}
# pickle preserves the tuple AND the set exactly
blob = pickle.dumps(data)
print("pickle:", pickle.loads(blob))
# pickle: {'id': 1, 'tags': ('a', 'b'), 'nums': {1, 2, 3}}
# json: tuples become lists, and sets aren't allowed at all
text = json.dumps({"id": 1, "tags": ["a", "b"]})
print("json: ", text)
# json: {"id": 1, "tags": ["a", "b"]}| Aspect | pickle | JSON |
|---|---|---|
| Format | binary | text |
| Readable by humans | no | yes |
| Other languages | no | yes |
| Custom classes / sets / tuples | yes | no |
| Safe on untrusted data | NO | yes |
🎯 Your Turn: Fill in the Blank
Complete the round trip through an in-memory buffer. Replace each ___, then run it.
# 🎯 YOUR TURN: replace each ___
import pickle
from io import BytesIO
scores = {"Ada": 95, "Ben": 82, "Cleo": 91}
buf = BytesIO()
pickle.___(scores, buf) # write the object into the buffer
buf.seek(0) # rewind before reading
restored = pickle.___(buf) # read the object back
print(restored)
print("Top scorer:", max(restored, key=restored.get))
# Expected output:
# {'Ada': 95, 'Ben': 82, 'Cleo': 91}
# Top scorer: Adaimport pickle
from io import BytesIO
scores = {"Ada": 95, "Ben": 82, "Cleo": 91}
buf = BytesIO()
pickle.dump(scores, buf)
buf.seek(0)
restored = pickle.load(buf)
print(restored) # {'Ada': 95, 'Ben': 82, 'Cleo': 91}
print("Top scorer:", max(restored, key=restored.get)) # AdaCommon Errors
❌ Opening the file in text mode
with open("data.pkl", "w") as f: # text mode!
pickle.dump(obj, f) # TypeError: write() argument must be str✅ Pickle is binary — use "wb" to write and "rb" to read:
with open("data.pkl", "wb") as f:
pickle.dump(obj, f)❌ Forgetting to seek(0) before loading
buf = BytesIO()
pickle.dump(obj, buf)
pickle.load(buf) # EOFError: cursor is at the END, nothing to read✅ Rewind the buffer first with buf.seek(0) (real files you reopen in "rb").
❌ Unpickling data you don't control
payload = download_from_internet()
pickle.loads(payload) # can run arbitrary code — DANGER✅ Never unpickle untrusted bytes. Use JSON for external data, and reserve pickle for your own trusted state.
🎯 Mini Challenge: A Game Save File
Build a GameState, pickle it into a buffer, then load it back and confirm the level, inventory, and a custom method all survived the round trip.
import pickle
from io import BytesIO
class GameState:
def __init__(self, level, hp, inventory):
self.level = level
self.hp = hp
self.inventory = inventory
def summary(self):
return f"Lvl {self.level}, {self.hp} HP, {len(self.inventory)} items"
# Save the game
state = GameState(7, 84, ["sword", "shield", "potion"])
save_file = BytesIO()
pickle.dump(state, save_file)
# Load the game later
save_file.seek(0)
loaded = pickle.load(save_file)
print("Loaded:", loaded.summary())
print("Inventory:", loaded.inventory)
print("Methods work:", loaded.summary() == state.summary())
# ✅ Expected output:
# Loaded: Lvl 7, 84 HP, 3 items
# Inventory: ['sword', 'shield', 'potion']
# Methods work: True📚 Keep Learning
- pickle — Python object serialization — dumps/loads, dump/load, protocols, and the prominent security note.
- json — the safe, portable alternative — for data that's shared or comes from outside your program.
- io.BytesIO — in-memory binary streams — the buffer we used to stand in for a file.
Frequently Asked Questions
Lesson complete — you can persist Python objects safely!
You can serialize with dumps/loads, save to files with dump/load, pickle custom objects, know what can't be pickled, pick a protocol, and — most importantly — you'll never unpickle untrusted data. You also know exactly when JSON is the safer choice.
Practice quiz
What type does pickle.dumps(obj) return?
- str
- dict
- bytes
- file
Answer: bytes. dumps serializes an object to a bytes blob; loads turns those bytes back into an object.
For a dict d, what is (pickle.loads(pickle.dumps(d)) is d)?
- False
- True
- None
- It raises an error
Answer: False. Unpickling builds a fresh, independent copy — it is equal (==) to the original but not the same object (is).
What is the difference between dumps/loads and dump/load?
- dump/load are faster versions of dumps/loads
- dumps/loads are only for dicts
- There is no difference
- dumps/loads work with bytes in memory; dump/load work with file objects
Answer: dumps/loads work with bytes in memory; dump/load work with file objects. The 's' versions return/accept a bytes string; the plain versions write to / read from a file-like object.
Which of these CANNOT be pickled?
- A dict of ints
- A lambda function
- A custom class instance
- A tuple of strings
Answer: A lambda function. Lambdas (and open files, sockets, generators) can't be pickled and raise PicklingError.
Why must you never pickle.loads data from an untrusted source?
- Unpickling can execute arbitrary code
- It is too slow
- It corrupts the data
- It only works on Windows
Answer: Unpickling can execute arbitrary code. A malicious pickle can run any code on your machine the moment you load it. Use JSON across trust boundaries.
What file mode must you use to write a pickle to disk?
- "w" (text)
- "a" (append)
- "wb" (binary)
- "r" (read)
Answer: "wb" (binary). Pickle produces bytes, so the file must be opened in binary mode — 'wb' to write, 'rb' to read.
After pickle.dump(obj, buf) on a BytesIO, what must you do before pickle.load(buf)?
- Nothing — load works immediately
- Call buf.seek(0) to rewind
- Call buf.flush()
- Re-create the buffer
Answer: Call buf.seek(0) to rewind. The cursor sits at the END after writing; seek(0) rewinds so load can read from the start (else EOFError).
What is pickle.HIGHEST_PROTOCOL in modern Python?
- 0
- 2
- 10
- 5
Answer: 5. Protocols are numbered 0 through 5; HIGHEST_PROTOCOL is 5, the newest and most efficient.
Which format preserves a Python tuple and set exactly through a round trip?
- JSON
- pickle
- CSV
- Both equally
Answer: pickle. pickle keeps exact Python types (tuples, sets, custom classes). JSON turns tuples into lists and rejects sets.
When is JSON the safer choice over pickle?
- When caching your own program's state
- When you need maximum speed
- When data is shared, stored long-term, or comes from outside
- When pickling custom classes
Answer: When data is shared, stored long-term, or comes from outside. JSON is text, language-neutral, and safe to parse — ideal for any data crossing a trust boundary.
Continue this course
- Previous: Time Zones with zoneinfo
- Next: Config Files with configparser — Read and write INI configuration with sections, types, and defaults
- Quick reference: Python cheat sheet
Frequently asked questions
What is pickling?
Pickling is Python's built-in way to serialize an object — turn it into a stream of bytes you can store or transmit — and unpickling turns those bytes back into a live object. Unlike JSON, pickle can handle almost any Python object, including custom class instances, tuples, sets, and nested structures, preserving their exact types.
Why is unpickling untrusted data dangerous?
Because unpickling can execute arbitrary code. A maliciously crafted pickle can run any command on your machine the moment you call pickle.loads on it. The rule is absolute: never unpickle data from an untrusted or unauthenticated source. For data crossing a trust boundary, use a safe format like JSON instead.
What's the difference between dumps/loads and dump/load?
dumps and loads work with bytes objects in memory: dumps(obj) returns bytes, loads(data) returns the object. dump and load work with file-like objects: dump(obj, file) writes the bytes to the file, load(file) reads them back. The 's' versions are 'to/from string of bytes'; the plain versions are 'to/from a file'.
When should I use pickle versus JSON?
Use pickle for trusted, Python-to-Python data where you need to preserve exact types — caching computed objects, saving program state between runs of your own code. Use JSON when humans or other languages need to read the data, or whenever it crosses a trust boundary, because JSON is text-based, language-neutral, and safe to parse.