The marshal module reads and writes an internal binary format used mainly by Python itself to store code objects in compiled files. It can represent several basic types, including numbers, strings, bytes, tuples, lists, sets, frozensets, dictionaries, and code objects, depending on the interpreter version.
Although it can look like a fast alternative to JSON or pickle, marshal was not designed as a general persistence format. Compatibility is not guaranteed for every type, internal details can change, and invalid or malicious input must not be loaded. Its appropriate uses are narrow: Python tooling, disposable caches, controlled experiments, and study of interpreter internals.
marshal and .pyc files
When Python compiles a module, it may create a bytecode file inside __pycache__. The file contains an import-system header plus a representation of a code object.
Do not treat an entire .pyc file as a plain marshal.dumps() result. The header contains metadata and changes over time. Prefer import utilities and specialized tools for compiled modules.
Basic values
The format supports a selected set of Python-native types.
import marshal
value = {
"name": "example",
"version": 1,
"items": [1, 2, 3],
}
data = marshal.dumps(value)
restored = marshal.loads(data)
print(restored)
A successful round trip preserves supported values, but it does not create a stable public contract.
dump and load
dump(value, file) writes to a binary stream and load(file) reads one object.
import marshal
with open("state.bin", "wb") as file:
marshal.dump(value, file)
with open("state.bin", "rb") as file:
value = marshal.load(file)
Always use binary mode. A text stream attempts character conversion and is incompatible with arbitrary bytes.
dumps and loads
dumps() returns bytes, while loads() accepts a bytes-like value.
payload = marshal.dumps((1, 2, 3))
value = marshal.loads(payload)
This form is convenient when storage, hashing, or transport already works with byte sequences.
Format versions
Writing functions accept a format version. The default corresponds to the current interpreter’s recommended format.
payload = marshal.dumps(value, 4)
Do not select a version merely because its number is larger. Consult the documentation for the exact Python runtime and test the destination environment.
Compatibility is limited
Python preserves selected simple values across format changes where practical, but code objects are not compatible across Python versions. Internal details can and do change.
Use a documented and versioned format when data must survive upgrades, cross service boundaries, or be read by another language.
Code objects
A code object contains bytecode, constants, names, local-variable information, and execution metadata.
import marshal
code = compile("result = 2 + 3", "example.py", "exec")
payload = marshal.dumps(code)
restored = marshal.loads(payload)
namespace = {}
exec(restored, namespace)
print(namespace["result"])
Executing the restored object executes code. Only do this with data produced and protected by your own trusted system.
allow_code
Current APIs provide an allow_code parameter that controls whether code objects may be serialized or deserialized.
data = marshal.dumps(value, allow_code=False)
restored = marshal.loads(data, allow_code=False)
Disabling code objects removes one category of risk but does not make hostile input safe.
Untrusted input
The official documentation warns against loading data from an untrusted source. A malformed payload can cause failures, excessive resource use, or other unsafe behavior.
Never call marshal.loads() on HTTP requests, public queue messages, user uploads, or shared cache entries without a strong trust boundary.
Integrity protection
When an internal file is important, protect it with file permissions and, when appropriate, a cryptographic MAC or signature. Integrity checks detect alteration.
A signature does not solve version incompatibility and is useful only when the key and producer are trusted.
Limit input size
Check file or message size before reading. Large structures can consume substantial memory and CPU.
from pathlib import Path
path = Path("state.bin")
if path.stat().st_size > 10_000_000:
raise ValueError("file is too large")
Choose a limit based on the real use case.
Depth and recursion
Very deeply nested values can exceed internal recursion limits or trigger errors. Do not raise recursion limits simply to accept arbitrary payloads.
Validate the logical structure after reading and prefer shallow schemas.
Unsupported objects
Instances of custom classes, regular functions, active connections, generators, and many other objects do not have a direct marshal representation.
try:
marshal.dumps(object())
except ValueError as error:
print(error)
Do not build a complicated conversion layer merely to force this format. JSON, dataclasses, or controlled pickle may describe the domain better.
Singleton values
Values such as None, True, and False are supported. After loading, application-level type and schema validation is still required.
Representable does not mean valid for your business logic.
Dictionaries
Mappings may contain supported keys and values. Observable order should not be treated as a file-format contract.
Configuration data is better represented by an explicit schema with named fields, types, and versions.
Sets
Sets and frozensets may be represented by supported format versions. Because sets have no semantic order, generated bytes are not a canonical content hash.
Normalize data according to a documented specification when deterministic signing is required.
Floating-point and complex numbers
The format supports Python numeric types but is not intended for cross-language interoperability.
When precise decimal or monetary data must be portable, serialize integers or strings under an explicit schema instead of depending on an internal representation.
marshal versus pickle
Pickle supports a broader range of objects and customization through tools such as Python copyreg. Both are Python-specific and unsafe for untrusted input.
marshal is narrower and more closely tied to interpreter internals, especially code objects.
marshal versus JSON
JSON supports fewer types but is documented, interoperable, and appropriate for APIs when combined with validation.
Use JSON for application data and communication. Use marshal only when Python-internal behavior is a real requirement.
marshal versus struct
struct packs values according to an explicit binary layout and is better for stable protocols and file formats.
Marshal describes Python values and does not expose a public layout intended for other implementations.
Disposable caches
A cache created and consumed by the same Python version can be an acceptable use. The system must be able to delete and rebuild it after any read failure.
Never make marshal data the only copy of important information.
Store external metadata
For an internal file, keep application version, Python version, checksum, and timestamp outside or alongside the payload.
metadata = {
"python": platform.python_version(),
"schema": 1,
}
This makes it possible to reject incompatible caches before deserialization.
Atomic writes
Write to a temporary file first, flush when durability matters, and replace the destination with os.replace().
A crash during direct writing can leave a truncated payload.
Concurrent access
Several processes must not overwrite one file without coordination. Use locks, versioned names, or a proper cache service.
Atomic replacement normally allows readers to observe either the old or the new file rather than partially written content.
Read failures
Handle expected errors, discard rebuildable caches, and retain diagnostics.
try:
value = marshal.loads(payload)
except (EOFError, ValueError, TypeError) as error:
record_invalid_cache(error)
value = rebuild()
Do not continue with partially read state.
Auditing
Marshal operations can emit Python auditing events. Controlled environments may observe object and code-object loading.
Audit hooks supplement permissions and isolation; they do not replace them.
Bytecode analysis
To study code objects, combine compile(), controlled marshal use, and the dis module. Never execute unknown bytecode.
Python ast explains a structural layer that exists before bytecode generation.
Cross-version tests
If a cache crosses deployments, test every supported Python version. Cover old-file reads, incompatible-file rejection, and automatic rebuilding.
A same-process round trip is not enough.
Observability
Record payload size, application version, Python version, duration, and invalidation reason. Do not log raw bytes, which may contain code or sensitive data.
Common mistakes
Common failures include using marshal as a database, reading external payloads, assuming code-object compatibility, treating a .pyc as a raw marshal payload, executing restored code without trust, omitting limits, and keeping important data only in this format.
Conclusion
marshal is an internal Python tool suited to code objects and disposable caches under strict control. Use binary streams, validate size and versions, protect the source, and rebuild caches after incompatibility.
For application data, prefer stable formats and explicit schemas. Consult the official marshal documentation and the dis documentation.







