跳到主要内容
版本:dev

Python 原生序列化

Python 原生序列化是通过 xlang=False 选择的仅限 Python 编码模式。当所有写入端和读取端都是 Python,并且载荷应遵循 Python 对象模型而非可移植的跨语言类型系统时,请使用该模式。

如果字节必须由 Java、C++、Go、Rust、JavaScript/TypeScript、C#、Swift、Dart、Scala、Kotlin 或其他非 Python Fory 实现读取,请使用 Python 默认的跨语言序列化模式。

何时使用原生序列化​

以下情况使用原生序列化:

  • 载荷仅由 Python 应用生成和使用。
  • 正在为仅限 Python 的对象图替代 pickle 或 cloudpickle。
  • 数据模型包含函数、lambda、本地类、方法或 Python 归约钩子。
  • 对象图可能包含需要 Python 引用跟踪的共享对象或循环。
  • 大型 Python 数据对象需要 pickle 协议 5 风格的带外缓冲区。

原生模式可以序列化全局函数、本地函数、lambda、本地类、方法,以及使用 __getstate__、__setstate__、__reduce__ 或 __reduce_ex__ 自定义的对象等 Python 专用值。这些值不是有效的跨语言载荷。

创建原生模式 Fory 实例​

创建 Fory 时设置 xlang=False:

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

对于已注册且可信的类型范围,请保持 strict=True。仅当原生模式载荷需要函数、本地类或由归约钩子重建的对象等动态 Python 类型时,才使用 strict=False。

常见用法​

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)

需要 pickle 风格 API 时使用 dumps/loads;在显式切换模式的代码中需要与跨语言 API 形式保持一致时,则使用 serialize/deserialize。

具名元组​

原生模式支持 typing.NamedTuple 和 collections.namedtuple,保留具体类及字段值。 严格模式下,需要在每个通信端注册具名元组类型:

from typing import NamedTuple
import pyfory

class Record(NamedTuple):
name: str
values: tuple
count: int

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

record = Record("sample", (1.0, 2.0, 3.0), 42)
restored = fory.loads(fory.dumps(record))
assert type(restored) is Record
assert restored == record

写入端与读取端必须使用相同的具名元组定义,包括字段顺序。

容器子类​

原生模式保留普通 list、set 和 dict 子类,包括内容、实例属性及继承的 __slots__。 应在首次操作前,在两个通信端注册具体子类:

import pyfory

class LabeledDict(dict):
pass

fory = pyfory.Fory(xlang=False, ref=True)
fory.register(LabeledDict, type_id=100)

value = LabeledDict(answer=42)
value.label = "example"
value["self"] = value
restored = fory.loads(fory.dumps(value))

assert type(restored) is LabeledDict
assert restored.label == "example"
assert restored["self"] is restored

普通子类路径不调用 __init__。通信两端必须使用相同的子类与 slot 定义。 启用 ref=True 可保留容器内容与属性之间的共享对象和循环引用。

即使重写迭代或修改方法,原生子类仍保留其基础容器存储。状态钩子在内容恢复后运行, 因此 __getstate__ 和 __setstate__ 只需描述实例状态。显式自定义序列化器和自定义归约钩子优先。 具有自定义 __new__、__getnewargs__ 或 __getnewargs_ex__ 的类需要自定义归约钩子 或自定义序列化器。collections.abc 接口不定义如何构造任意具体类; 若普通对象状态或钩子无法描述完整值,应使用显式序列化器。

声明为 Mapping、Sequence 或 Set 的字段使用集合值语义,返回内置 dict、list 或 set。 需要保留 Python 类型身份与状态时,应声明已注册的具体子类或使用动态字段。 Xlang 模式中的容器子类同样使用集合值语义,不保留 Python 专属实例状态。

安全与动态类型​

原生模式可以重建会在反序列化期间执行导入和构造逻辑的 Python 对象。应像对待不可信 pickle 字节一样对待不可信原生模式字节。

  • 反序列化只应包含已注册或内置类型的数据时,保持 strict=True。
  • 仅对需要动态 Python 类或函数的可信载荷使用 strict=False。
  • 需要动态类型但仍应限制可接受类型范围时,提供 policy= 反序列化策略。
  • 不要将跨语言/原生模式选择用作安全控制。应根据载荷来源应用严格模式、策略、注册和资源限制。

Python 专用值与钩子​

可调用对象和类型值参见函数、类与方法,归约、状态、构造和 pickle/cloudpickle 迁移参见序列化钩子。

引用与循环​

需要往返保留对象标识、共享引用或循环时,启用 ref=True:

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

不需要保留对象标识的值形载荷应禁用引用跟踪,这样可以减小载荷并简化热点路径。

带外缓冲区​

对于大型二进制载荷和由外部内存支撑的数据结构,Python 原生模式可以使用 pickle 协议 5 风格的带外缓冲区:

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

载荷只在 Python 中使用,并且大型缓冲区应避免额外复制时,使用此方式。参见带外序列化。

原生模式与跨语言模式对比​

需求使用原生序列化使用跨语言序列化
仅限 Python 的载荷是可选
非 Python 读取端或写入端否是
函数、lambda、本地类是否
__reduce__ / __getstate__ 对象钩子是否
替代 Pickle/cloudpickle是否
跨语言可移植类型映射否是

性能对比​

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")

故障排除​

其他语言无法读取载荷​

写入端正在使用原生序列化。请使用 xlang=True 重新构建,在每个对等端注册可移植 Schema,并避免使用 lambda 或本地类等仅限 Python 的值。

动态类或函数反序列化失败​

对可信载荷使用 strict=False;只应接受选定动态类型时,提供反序列化 policy=。

循环无法往返保留​

创建 Fory 实例时设置 ref=True。

值依赖 pickle 钩子​

保持载荷使用原生模式。跨语言模式不会执行 Python __reduce__、__reduce_ex__、__getstate__ 或 __setstate__ 对象重建钩子。

相关主题​