Python marshal: Internal Binary Format

Published on: August 27, 2026
Reading time: 6 minutes
A person in a hoodie coding on dual monitors, depicting cybersecurity and hacking themes.

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.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    A close-up view of fresh, green cucumbers ready for pickling and preservation in Estonia.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python copyreg: Customize Pickle Types

    Learn Python copyreg to customize pickle reducers, version serialized state, avoid global conflicts, and test safely across processes.

    Ler mais

    Tempo de leitura: 6 minutos
    27/08/2026
    Close-up of a python snake coiled in darkness, showcasing its scales and eyes.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python reprlib: Safe Object Summaries

    Learn Python reprlib to summarize large lists, strings, and recursive objects while keeping logs safe, bounded, and readable.

    Ler mais

    Tempo de leitura: 5 minutos
    27/08/2026
    A close-up shot showcasing the intricate scales of a snake, highlighting texture and color.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python graphlib: Topological Task Order

    Learn Python graphlib to order dependencies, detect cycles, run ready tasks in parallel, and build safe task pipelines.

    Ler mais

    Tempo de leitura: 6 minutos
    27/08/2026
    A developer typing code on a laptop with a Python book beside in an office.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    weakref: Avoid Retaining Objects in Caches

    Learn Python weakref for weak references, caches, WeakSet, WeakMethod, finalize callbacks, and avoiding accidental object retention.

    Ler mais

    Tempo de leitura: 7 minutos
    27/08/2026
    Red leader figure connected to wooden figures, emphasizing leadership and teamwork.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python contextlib: Manage Resources Safely

    Learn Python contextlib with contextmanager, ExitStack, suppress, closing, asynccontextmanager, and safe resource cleanup.

    Ler mais

    Tempo de leitura: 6 minutos
    27/08/2026
    A developer typing code on a laptop with a Python book beside in an office.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python ast: Analyze and Transform Code

    Learn Python ast to analyze and transform source code, build visitors, preserve positions, use literal_eval, and avoid execution risks.

    Ler mais

    Tempo de leitura: 6 minutos
    27/08/2026