Skip to main content
Version: 1.6

Python Native Serialization

Python native serialization is the Python-only wire mode selected with xlang=False. Use it when every writer and reader is Python and the payload should follow Python's object model instead of the portable xlang type system.

Use Cross-Language Interoperability, the default Python mode, when bytes must be read by Java, C++, Go, Rust, JavaScript/TypeScript, C#, Swift, Dart, Scala, Kotlin, or another non-Python Fory implementation.

When To Use Native Serialization​

Use native serialization when:

  • A payload is produced and consumed only by Python applications.
  • You are replacing pickle or cloudpickle for Python-only object graphs.
  • The data model includes functions, lambdas, local classes, methods, or Python reduction hooks.
  • The graph can contain shared objects or cycles that need Python reference tracking.
  • You need pickle protocol 5-style out-of-band buffers for large Python data objects.

Native mode can serialize Python-specific values such as global functions, local functions, lambdas, local classes, methods, and objects customized with __getstate__, __setstate__, __reduce__, or __reduce_ex__. Those values are not valid xlang payloads.

Create a Native-Mode Fory Instance​

Create Fory with xlang=False:

import pyfory
fory = pyfory.Fory(xlang=False, ref=False, strict=True)

Keep strict=True for registered, trusted type surfaces. Use strict=False only when native-mode payloads need dynamic Python types such as functions, local classes, or objects reconstructed by reduction hooks.

Common Usage​

import pyfory

fory = pyfory.Fory(xlang=False, ref=True, strict=False)

data = fory.dumps({"name": "Alice", "age": 30, "scores": [95, 87, 92]})
print(fory.loads(data))

from dataclasses import dataclass

@dataclass
class Person:
name: str
age: int

person = Person("Bob", 25)
data = fory.dumps(person)
print(fory.loads(data)) # Person(name='Bob', age=25)

Use dumps/loads for pickle-style APIs, or serialize/deserialize when matching the xlang API shape in code that switches modes explicitly.

Security And Dynamic Types​

Native mode can reconstruct Python objects that execute import and construction logic during deserialization. Treat untrusted native-mode bytes the same way you would treat untrusted pickle bytes.

  • Keep strict=True when deserializing data that should contain only registered or built-in types.
  • Use strict=False only for trusted payloads that require dynamic Python classes or functions.
  • Provide a policy= deserialization policy when dynamic types are required but the accepted type surface should still be restricted.
  • Do not use xlang/native mode choice as a security control. Apply strict mode, policies, registration, and resource limits based on the payload source.

Python-specific values and hooks​

See Functions, Classes, and Methods for callable and type values, then Serialization Hooks for reduction, state, construction, and pickle/cloudpickle migration.

References And Cycles​

Enable ref=True when object identity, shared references, or cycles must round-trip:

import pyfory

fory = pyfory.Fory(xlang=False, ref=True, strict=True)

node = {}
node["self"] = node
data = fory.dumps(node)
decoded = fory.loads(data)
assert decoded["self"] is decoded

Disable reference tracking for value-shaped payloads that do not need identity preservation. It keeps the payload smaller and the hot path simpler.

Out-of-Band Buffers​

Python native mode can use pickle protocol 5-style out-of-band buffers for large binary payloads and data structures backed by external memory:

import pickle
import pyfory

data = b"Large binary data"
pickle_buffer = pickle.PickleBuffer(data)

buffer_objects = []
fory = pyfory.Fory(xlang=False, ref=True, strict=False)
serialized = fory.dumps(pickle_buffer, buffer_callback=buffer_objects.append)
buffers = [obj.getbuffer() for obj in buffer_objects]
decoded = fory.loads(serialized, buffers=buffers)
assert bytes(decoded.raw()) == data

Use this when the payload stays in Python and large buffers should avoid extra copies. See Out-of-Band Serialization.

Native And Xlang Comparison​

RequirementUse native serializationUse xlang serialization
Python-only payloadsYesOptional
Non-Python readers or writersNoYes
Functions, lambdas, local classesYesNo
__reduce__ / __getstate__ object hooksYesNo
Pickle/cloudpickle replacementYesNo
Portable type mapping across languagesNoYes

Performance Comparison​

import pyfory
import pickle
import timeit

fory = pyfory.Fory(xlang=False, ref=True, strict=False)

obj = {f"key{i}": f"value{i}" for i in range(10000)}
print(f"Fory: {timeit.timeit(lambda: fory.dumps(obj), number=1000):.3f}s")
print(f"Pickle: {timeit.timeit(lambda: pickle.dumps(obj), number=1000):.3f}s")

Troubleshooting​

Another language cannot read the payload​

The writer is using native serialization. Rebuild it with xlang=True, register portable schemas on every peer, and avoid Python-only values such as lambdas or local classes.

A dynamic class or function fails to deserialize​

Use strict=False for trusted payloads and provide a deserialization policy= when only selected dynamic types should be accepted.

A cycle does not round-trip​

Create the Fory instance with ref=True.

A value depends on pickle hooks​

Keep the payload in native mode. Xlang mode does not execute Python __reduce__, __reduce_ex__, __getstate__, or __setstate__ object reconstruction hooks.