Pickle protocol default change in Python 3.8 creates restoration barrier for cross-version backups
0 reputation · 11 Jan 2023, 09:19 UTC
0 reputation · 11 Jan 2023, 09:19 UTC
The pickle module's default protocol advanced from version 4 to version 5 in Python 3.8, while earlier runtimes such as Python 3.3–3.7 cannot deserialize protocol 5 streams. Applications that rely on the implicit default for long-term backups therefore face a hard compatibility boundary when the restore environment runs an older interpreter. The format also lacks embedded integrity checksums, so a corrupted byte stream may either raise an exception or silently produce incorrect objects without detection.
Given these constraints, what explicit protocol version should be pinned to maximize forward and backward compatibility across a fleet that includes Python 3.3 through 3.12? How can an application verify that a restored object graph matches the original structure when the pickle format provides no built-in validation? What migration strategy allows existing protocol 5 archives to be read on newer runtimes while new backups use a portable protocol?
33275 reputation · 11 Jan 2023, 20:41 UTC
Use protocol=3 explicitly for all new backups. It is the highest protocol supported by Python 3.3 (released 2012) and remains readable through Python 3.12 and later. Protocol 4 requires Python 3.4+; protocol 5 requires Python 3.8+. Never rely on pickle.DEFAULT_PROTOCOL or pickle.HIGHEST_PROTOCOL—they change per interpreter version.
import pickle
# Writing
with open('backup.pkl', 'wb') as f:
pickle.dump(obj, f, protocol=3)
# Reading (works on 3.3–3.12+)
with open('backup.pkl', 'rb') as f:
obj = pickle.load(f)
Assumption: your fleet still includes Python 3.3 runtimes. If you can drop 3.3 support, protocol=4 is preferable—it adds efficient 64-bit frame support (PEP 3154) for objects larger than 4 GiB and is supported on 3.4–3.12.
Pickle provides no integrity checksums. Implement two-layer verification:
isinstance/hasattr, Pydantic models, or attrs validators) and assert zero mismatches on known-good archives.import hashlib, hmac, pickle
SECRET = b'your-secret-key'
def save(obj, path):
data = pickle.dumps(obj, protocol=3)
mac = hmac.new(SECRET, data, hashlib.sha256).digest()
with open(path, 'wb') as f:
f.write(len(data).to_bytes(8, 'big'))
f.write(data)
f.write(mac)
def load(path):
with open(path, 'rb') as f:
length = int.from_bytes(f.read(8), 'big')
data = f.read(length)
stored_mac = f.read()
expected_mac = hmac.new(SECRET, data, hashlib.sha256).digest()
if not hmac.compare_digest(stored_mac, expected_mac):
raise ValueError('Integrity check failed')
obj = pickle.loads(data)
# structural validation here
return obj
Run a one-time offline conversion on a Python 3.8+ host:
pickle.load it (protocol 5 is natively supported).protocol=3 and write as a new versioned backup (e.g., backup.v2.pkl).==).UnpicklingError, EOFError, or MemoryError for manual review.import pickle, copy
for old_path in old_archives:
with open(old_path, 'rb') as f:
original = pickle.load(f) # protocol 5
migrated = pickle.dumps(original, protocol=3)
with open(new_path, 'wb') as f:
f.write(migrated)
# optional deep equality check
reloaded = pickle.loads(migrated)
assert copy.deepcopy(original) == reloaded
After migration, all new backups use protocol 3. Consider replacing pickle for long-term archives with a self-describing, schema-validated format (msgpack with schema, CBOR, or JSON + jsonschema) to avoid pickle-specific versioning and security risks.
Does your object graph contain any single object larger than 4 GiB? If yes, protocol 3 cannot serialize it; you must either drop Python 3.3 support (use protocol 4) or split the object before pickling.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.