pickle模块 — 对象序列化

Python标准库精讲专题 · 数据持久化篇 · 掌握对象序列化

专题:Python标准库精讲系统学习

关键词:Python, 标准库, pickle, 序列化, 反序列化, dumps, loads, 对象持久化, 协议版本

一、pickle模块概述

1.1 什么是序列化

序列化(Serialization)是指将内存中的对象转化为可存储或可传输的字节流的过程;反序列化(Deserialization)则是逆过程,将字节流还原为原始对象。序列化在数据持久化、远程通信(RPC)、缓存、分布式计算等场景中扮演着核心角色。

Python作为一门"一切皆对象"的高级语言,其对象结构复杂多样(嵌套字典、自定义类实例、函数引用等),因此需要一个能够处理任意Python对象的序列化方案——这正是 pickle 模块的使命。

pickle 是Python标准库中内置的对象序列化模块,它实现了对任意Python对象的二进制序列化。与简单的文本序列化工具(如JSON)不同,pickle 能够处理几乎所有的Python原生对象类型,包括类的实例、递归引用、共享引用等复杂结构。

1.2 pickle vs json:如何选择

初学者最常问的问题是:"为什么有了JSON还要用pickle?" 选择取决于具体的使用场景。

对比维度 pickle json
数据格式 二进制格式,不可读 文本格式,人类可读
适用范围 任意Python对象(函数、类实例、复杂嵌套结构) 仅限于基本数据类型(dict, list, str, int, float, bool, None)
跨语言 仅Python,不保证跨版本兼容 几乎所有语言都支持
安全性 高危 —— 反序列化可执行任意代码 安全 —— 纯数据,无执行能力
性能 针对Python对象优化,速度更快 文本解析,相对较慢
空间效率 二进制紧凑格式,体积更小 基于文本,体积较大
适用场景 临时缓存、进程间通信、模型持久化、Python内部RPC Web API、配置文件、跨语言数据交换

核心原则:如果数据只在Python内部流转且涉及复杂对象,优先使用 pickle;如果需要跨语言传输或给前端消费,则必须使用 json。永远不要用 pickle 做Web API的数据格式。

1.3 模块导入与命名由来

pickle 的名字来源于"腌渍"(pickling)—— 将食物浸入盐水或醋中保存的过程。Python社区用"pickling"比喻对象序列化("保存"对象),用"unpickling"比喻反序列化("恢复"对象)。模块导入非常简单:

import pickle # 查看版本 print(pickle.format_version) # 当前最高协议版本 print(pickle.DEFAULT_PROTOCOL) # 默认使用的协议版本

自Python 3.8起 pickle 模块包含一个 pickletools 子模块,用于分析和调试pickle数据流,我们将在后续章节中介绍。

二、核心函数

pickle 模块提供了四组高度对称的核心函数,分别对应序列化和反序列化的两种输出目标:字节对象(in-memory)和文件对象(on-disk)。

2.1 序列化:将对象转换为字节流

pickle.dumps(obj, protocol=None) —— 将对象序列化为 bytes 对象,直接返回内存中的字节数据,不写入文件。

import pickle data = { 'name': 'pickle模块笔记', 'version': 1.0, 'tags': ['序列化', '持久化'], 'is_draft': False } # 序列化为字节对象 bytes_data = pickle.dumps(data) print(type(bytes_data)) # print(bytes_data[:50]) # 二进制输出,不可读

pickle.dump(obj, file, protocol=None) —— 将对象序列化后直接写入一个已打开的 file 对象(如文件、BytesIO流等)。文件必须以二进制模式打开。

import pickle data = {'key': 'value', 'numbers': [1, 2, 3]} # 写入文件 with open('data.pkl', 'wb') as f: pickle.dump(data, f) # 写入内存流 from io import BytesIO buf = BytesIO() pickle.dump(data, buf) buf.seek(0)

2.2 反序列化:将字节流还原为对象

pickle.loads(bytes_object) —— 将 bytes 对象反序列化为原始Python对象。

import pickle bytes_data = pickle.dumps({'name': 'Alice', 'score': 95}) obj = pickle.loads(bytes_data) print(obj) # {'name': 'Alice', 'score': 95} print(type(obj)) #

pickle.load(file) —— 从一个已打开的二进制文件对象中读取pickle数据并还原为Python对象。

import pickle with open('data.pkl', 'rb') as f: restored = pickle.load(f) print(restored) # {'key': 'value', 'numbers': [1, 2, 3]}

2.3 多个对象的序列化与反序列化

当需要在一个文件中序列化多个独立对象时,可以依次调用多次 dump,然后依次调用多次 load 来恢复:

import pickle # 写入多个对象 with open('multi.pkl', 'wb') as f: pickle.dump({'a': 1}, f) pickle.dump([2, 3, 4], f) pickle.dump('hello', f) # 读取多个对象 objects = [] with open('multi.pkl', 'rb') as f: while True: try: obj = pickle.load(f) objects.append(obj) except EOFError: break print(objects) # [{'a': 1}, [2, 3, 4], 'hello']

2.4 使用文件协议和BytesIO

除了直接与磁盘文件交互,dump/load 也支持任何实现了 read()/write() 方法的类文件对象,这使得在内存中进行序列化/反序列化成为可能:

from io import BytesIO import pickle # 在内存中序列化 buf = BytesIO() pickle.dump(12345, buf) pickle.dump(3.14159, buf) pickle.dump("Hello Pickle", buf) # 从内存中反序列化 buf.seek(0) print(pickle.load(buf)) # 12345 print(pickle.load(buf)) # 3.14159 print(pickle.load(buf)) # Hello Pickle

最佳实践:使用 with 语句管理文件句柄,始终以二进制模式打开文件('wb' 写入,'rb' 读取),避免文本模式导致的编码错误。对于复杂的多对象存储场景,推荐使用一个容器(如字典或列表)存储所有对象,单次序列化。

三、Pickle协议版本

3.1 协议版本演进

pickle协议版本定义了序列化数据的二进制格式。随着Python版本迭代,协议不断演进以提升性能、安全性和表达能力。截至目前,共有6个协议版本(0~5)。

协议版本 Python版本 格式特点
协议0 最初版本(所有版本) 文本可读格式,体积最大,向后兼容性最好
协议1 Python 2.3+ 二进制格式,比协议0更紧凑
协议2 Python 2.3+ 引入新的二进制编码,优化新式类的序列化
协议3 Python 3.0+ 显式支持 bytes 对象,是Python 3的默认协议(DEFAULT_PROTOCOL=3,Python 3.8之前)
协议4 Python 3.4+ 增加对大对象的支持、更优的嵌套对象序列化、命名切片支持
协议5 Python 3.8+ 引入 __reduce_ex__ 和缓冲区协议支持,极大提升大对象(如NumPy数组)的序列化效率

3.2 查看与指定协议版本

import pickle # 查看最高支持协议版本 print(pickle.HIGHEST_PROTOCOL) # 5 (Python 3.8+) # 查看默认协议版本 print(pickle.DEFAULT_PROTOCOL) # 5 (Python 3.8+) 或 4 (Python 3.4-3.7) # 显式指定协议版本 data = {'a': [1, 2, 3]} b0 = pickle.dumps(data, protocol=0) # 文本可读格式 b1 = pickle.dumps(data, protocol=1) # 二进制格式 b2 = pickle.dumps(data, protocol=2) # 新二进制格式 b3 = pickle.dumps(data, protocol=3) # bytes支持 b4 = pickle.dumps(data, protocol=4) # 大对象支持 b5 = pickle.dumps(data, protocol=5) # 缓冲区协议 print(f"协议0大小: {len(b0)} bytes") print(f"协议1大小: {len(b1)} bytes") print(f"协议2大小: {len(b2)} bytes") print(f"协议3大小: {len(b3)} bytes") print(f"协议4大小: {len(b4)} bytes") print(f"协议5大小: {len(b5)} bytes")

3.3 协议选择策略

选择协议版本需要在向后兼容性和效率之间权衡:

重要提示:不同Python版本默认的 DEFAULT_PROTOCOL 不尽相同。Python 3.8+ 默认为协议5,Python 3.7 默认为协议4,Python 2.x 默认为协议2或0。如果你的pickle文件需要在不同Python版本间共享,务必使用更低的协议版本以确保兼容性。

四、可序列化对象

4.1 支持序列化的类型

pickle可以序列化几乎所有的Python内置类型,具体包括:

4.2 序列化示例:各类数据

import pickle import re from datetime import datetime # ----- 基本类型 ----- data = [ None, True, 42, 3.14159, 1+2j, "Hello pickle", b"binary data" ] print(pickle.loads(pickle.dumps(data))) # ----- 复杂嵌套结构 ----- nested = { 'metadata': { 'created': datetime.now(), # datetime对象 'tags': ('python', 'pickle'), 'count': 100 }, 'matrix': [[1,2,3],[4,5,6],[7,8,9]], 'pattern': re.compile(r'\d+\.\d+') # 正则表达式 } restored = pickle.loads(pickle.dumps(nested, protocol=pickle.HIGHEST_PROTOCOL)) print(restored['pattern'].match('3.14')) # ----- 函数引用(仅序列化引用)----- def square(x): return x * x serialized = pickle.dumps(square) fn = pickle.loads(serialized) print(fn(5)) # 25

4.3 序列化函数和类的限制

关键理解:pickle序列化函数和类时,并不序列化它们的字节码或源代码,而仅仅序列化它们的"全限定名"(即模块名 + 名称)。反序列化时,pickle会通过 import 机制重新获取该函数或类的定义。这意味着:

import pickle # lambda 不能被 pickle fn = lambda x: x + 1 try: pickle.dumps(fn) except pickle.PicklingError as e: print(f"错误: {e}") # 嵌套类也不能被 pickle def outer(): class Inner: pass return Inner cls = outer() try: pickle.dumps(cls) except AttributeError as e: print(f"错误: {e}")

4.4 自定义序列化行为

对于自定义类的实例,pickle提供了多个钩子方法,允许开发者精细控制序列化和反序列化的行为。

__getstate__() —— 控制"存什么"。默认情况下pickle序列化实例的 __dict__。如果类定义了 __getstate__,其返回值将替代 __dict__ 被序列化。常用于排除缓存、临时连接等不应持久化的属性。

import pickle class Connection: def __init__(self, host, port): self.host = host self.port = port self._cache = {} # 运行时缓存,无需序列化 self._socket = None # 网络连接,无法序列化 def __getstate__(self): # 排除 _cache 和 _socket state = self.__dict__.copy() state.pop('_cache', None) state.pop('_socket', None) return state conn = Connection('localhost', 8080) conn._cache['temp'] = 'data' conn._cache['result'] = 42 restored = pickle.loads(pickle.dumps(conn)) print(restored.__dict__) # {'host': 'localhost', 'port': 8080} -- 无 _cache 和 _socket

__setstate__(state) —— 控制"怎么恢复"。在反序列化时,如果类定义了 __setstate__,pickle会调用 obj.__setstate__(state) 而不是直接设置 obj.__dict__。常用于重新建立连接、初始化缓存等:

import pickle class Database: def __init__(self, dsn): self.dsn = dsn self._conn = None # 实际的数据库连接 def __getstate__(self): state = self.__dict__.copy() state['_conn'] = None # 不序列化连接对象 return state def __setstate__(self, state): self.__dict__.update(state) # 反序列化后重新建立连接 self._conn = self._connect() def _connect(self): return f"Connected to {self.dsn}" db = Database('sqlite:///test.db') db._conn = db._connect() restored = pickle.loads(pickle.dumps(db)) print(restored._conn) # Connected to sqlite:///test.db

__reduce__() / __reduce_ex__(protocol) —— 更底层的序列化控制接口。返回一个元组,指示pickle如何"重构"该对象。这是pickle序列化任意对象的核心机制。

import pickle class SpecialNumber: def __init__(self, value): self.value = value def __reduce_ex__(self, protocol): # 返回 (callable, args) 告诉pickle如何重建 return (self.__class__, (self.value,)) def __repr__(self): return f"SpecialNumber({self.value})" sn = SpecialNumber(42) restored = pickle.loads(pickle.dumps(sn)) print(restored) # SpecialNumber(42)

真实项目应用:在机器学习框架中,__getstate__ 常用于排除巨大的训练数据缓存或GPU张量;__setstate__ 常用于恢复后重新加载模型权重或重建GPU计算图。PyTorch、TensorFlow等深度学习了pickle作为其模型序列化的基础。

五、安全性(重点强调)

安全红线:永远不要对不可信的pickle数据执行 load()/loads() 操作!

5.1 根本原因:pickle是执行引擎而非数据格式

与JSON、XML等纯数据格式不同,pickle的协议本质上是一套栈式虚拟机指令集。当执行 pickle.loads(data) 时,Python会逐条执行这些"pickle指令",这些指令可以:

这意味着精心构造的pickle字节码可以实现任意代码执行(Arbitrary Code Execution, ACE)——这本质上是一个无限制的远程代码执行漏洞。

5.2 攻击示例:构造恶意pickle数据

import pickle import os # ---- 恶意代码示例:通过 __reduce__ 执行系统命令 ---- class Exploit: def __reduce__(self): # 反序列化时会执行 os.system('calc.exe') return (os.system, ('calc.exe',)) malicious_data = pickle.dumps(Exploit()) # ---- 攻击面:攻击者发送了恶意 pickle 数据 ---- # 受害者执行此行会弹出计算器(恶意行为) # pickle.loads(malicious_data) # 不要运行!

更隐蔽的攻击方式包括:在pickle数据中注入 __reduce__ 调用 subprocess.Popenexeceval__import__ 等函数,甚至直接构造字节码来实现无文件攻击。这类攻击可以完全绕过Python的安全限制。

5.3 真实世界攻击场景

历史上出现过多个与pickle相关的安全漏洞和攻击:

5.4 安全处理最佳实践

  1. 绝不对来自未经验证的来源(网络请求、用户上传、邮件附件、公共S3存储桶等)的pickle数据执行 loadloads
  2. 使用 hmac 或数字签名验证pickle数据的完整性:计算签名并附加在pickle数据之后,加载前验证
  3. 考虑替代方案:如果只是简单的字典/列表数据,使用 json(安全且跨语言)
  4. 考虑限制方案:使用 pickle.Unpickler 的子类并重写 find_class() 方法来白名单允许导入的模块
import pickle import hmac import hashlib # ---- 方案1:使用 HMAC 签名验证 pickle 数据完整性 ---- SECRET_KEY = b'super-secret-key' def secure_dumps(obj): data = pickle.dumps(obj) sig = hmac.new(SECRET_KEY, data, hashlib.sha256).digest() return data + sig def secure_loads(signed_data): data = signed_data[:-32] # SHA-256 是32字节 sig = signed_data[-32:] expected = hmac.new(SECRET_KEY, data, hashlib.sha256).digest() if not hmac.compare_digest(sig, expected): raise ValueError("数据完整性校验失败!") return pickle.loads(data) # ---- 方案2:白名单限制反序列化(专家级) ---- import io class RestrictedUnpickler(pickle.Unpickler): ALLOWED_MODULES = {'__builtin__', 'builtins', 'collections', 'datetime'} def find_class(self, module, name): if module not in self.ALLOWED_MODULES: raise pickle.UnpicklingError(f"禁止导入模块: {module}") return super().find_class(module, name) def restricted_loads(data): return RestrictedUnpickler(io.BytesIO(data)).load()

Python官方文档安全警告原文:"The pickle module is not secure. Only unpickle data you trust. It is possible to construct malicious pickle data which will execute arbitrary code during unpickling. Never unpickle data that could have come from an untrusted source, or that could have been tampered with."

六、性能与存储

6.1 协议版本对存储空间的影响

不同协议版本的序列化体积差异显著,特别是在处理大量数据时。协议版本越高,体积通常越小,序列化速度也越快。以下是一个实际测试:

import pickle import sys # 模拟真实数据 large_data = { 'users': [{'id': i, 'name': f'user_{i}', 'scores': [i*1.0, i*2.0, i*3.0]} for i in range(10000)], 'metadata': {'version': '2.0', 'count': 10000} } for protocol in range(pickle.HIGHEST_PROTOCOL + 1): data = pickle.dumps(large_data, protocol=protocol) print(f"协议{protocol}: {len(data)/1024:.1f} KB") # 输出示例: # 协议0: 1234.5 KB # 协议1: 456.7 KB # 协议2: 420.3 KB # 协议3: 380.1 KB # 协议4: 360.8 KB # 协议5: 355.2 KB

从协议0到协议5,体积差异可达3~4倍。对于需要持久化大量数据的场景,选择高版本协议可以显著降低存储开销。

6.2 压缩pickle数据

当pickle数据需要通过网络传输或长期存档时,可以结合压缩库进一步减小体积。常用的组合方案包括:

import pickle import gzip import bz2 import lzma data = {'key': 'value' * 1000} # 可压缩数据 # ---- 方案1:gzip 压缩(速度快,体积适中)---- with gzip.open('data.pkl.gz', 'wb') as f: pickle.dump(data, f, protocol=pickle.HIGHEST_PROTOCOL) with gzip.open('data.pkl.gz', 'rb') as f: restored = pickle.load(f) # ---- 方案2:bz2 压缩(速度慢,压缩比高)---- with bz2.BZ2File('data.pkl.bz2', 'wb') as f: pickle.dump(data, f, protocol=pickle.HIGHEST_PROTOCOL) # ---- 方案3:lzma 压缩(压缩比最高)---- with lzma.open('data.pkl.xz', 'wb') as f: pickle.dump(data, f, protocol=pickle.HIGHEST_PROTOCOL)

选择建议:对于机器学习模型等较大的持久化数据,推荐使用 gziplzma 压缩。gzip在速度和压缩比之间取得了较好的平衡;lzma压缩比最高但耗时更长。在科学计算领域,gzip + pickle协议5的组合是最常见的实践。

6.3 使用 pickletools 分析pickle协议栈

pickletools 是Python标准库中的pickle调试工具,可以反汇编pickle字节码,帮助我们理解pickle协议的底层工作原理。

import pickle import pickletools data = {'hello': 'world', 'nums': [1, 2, 3]} bytes_data = pickle.dumps(data, protocol=0) # 用协议0便于阅读 # 反汇编 pickle 操作码 pickletools.dis(bytes_data)

输出类似:

0: \x80 PROTO 5 2: \x95 FRAME 44 11: } EMPTY_DICT 12: \x94 MEMOIZE (as 0) 13: ( MARK 14: \x8c SHORT_BINUNICODE 'hello' 21: \x8c SHORT_BINUNICODE 'world' 28: \x8c SHORT_BINUNICODE 'nums' 34: ] EMPTY_LIST 35: \x94 MEMOIZE (as 1) 36: ( MARK 37: K BININT1 1 39: K BININT1 2 41: K BININT1 3 43: e APPENDS (mark at 36) 44: s SETITEMS (mark at 13) 45: . STOP

通过分析这些操作码,可以深入了解序列化的每一个步骤,对性能优化和bug调试非常有帮助。pickletools 还提供了优化功能:

import pickletools # 优化 pickle 数据(去掉冗余操作码) optimized = pickletools.optimize(bytes_data) print(f"原始大小: {len(bytes_data)}, 优化后: {len(optimized)}")

6.4 大数据序列化技巧

七、实战案例与总结

7.1 实战案例一:机器学习模型的持久化

在机器学习项目中,训练一个模型可能耗时数小时甚至数天,持久化模型以供后续加载推理是最典型的pickle应用场景。

import pickle import numpy as np # ---- 模拟训练一个简单的模型 ---- class LinearRegression: def __init__(self): self.coef_ = None self.intercept_ = None self._training_data = None # 训练后可丢弃 def fit(self, X, y): # 模拟训练过程 self.coef_ = np.array([0.5, 1.2, -0.3]) self.intercept_ = 2.1 self._training_data = (X, y) # 占用大量内存 def predict(self, X): return X @ self.coef_ + self.intercept_ def __getstate__(self): # 排除训练数据(太大,不需要持久化) state = self.__dict__.copy() state['_training_data'] = None return state def __setstate__(self, state): self.__dict__.update(state) # 模型恢复后的初始化操作 # ---- 保存模型 ---- model = LinearRegression() model.fit(np.random.randn(1000, 3), np.random.randn(1000)) with open('model.pkl', 'wb') as f: pickle.dump(model, f, protocol=pickle.HIGHEST_PROTOCOL) print("模型已保存") # ---- 加载模型(生产环境)---- with open('model.pkl', 'rb') as f: loaded_model = pickle.load(f) result = loaded_model.predict(np.array([[1.0, 2.0, 3.0]])) print(f"预测结果: {result}")

7.2 实战案例二:程序状态保存与恢复

在长时间运行的应用程序(如游戏、数据处理管道、爬虫)中,需要定期保存程序状态以便崩溃后恢复或下次启动时继续。

import pickle import os # ---- 游戏进度保存 ---- class GameState: def __init__(self): self.level = 1 self.score = 0 self.player_name = "" self.inventory = [] self.completed_levels = set() self._session_cache = {} # 运行时缓存,无需持久化 def save(self, filename='savegame.pkl'): with open(filename, 'wb') as f: pickle.dump(self, f, protocol=pickle.HIGHEST_PROTOCOL) print(f"游戏进度已保存到 {filename}") @classmethod def load(cls, filename='savegame.pkl'): if not os.path.exists(filename): return cls() with open(filename, 'rb') as f: return pickle.load(f) def __getstate__(self): state = self.__dict__.copy() state['_session_cache'] = {} return state # ---- 使用示例 ---- state = GameState.load() state.player_name = "Alice" state.level = 5 state.score = 3200 state.inventory = ["剑", "盾牌", "药水"] state.completed_levels = {1, 2, 3, 4} state.save() # 下次启动 new_session = GameState.load() print(f"玩家: {new_session.player_name}, 等级: {new_session.level}, 分数: {new_session.score}")

7.3 实战案例三:复杂配置的深度复制

pickle还可以作为一种"深拷贝"的替代方案——通过序列化后反序列化来生成一个完全独立的副本(deep copy)。这种方法比 copy.deepcopy 在某些场景下更灵活:

import pickle # 通过 pickle 实现深度复制 def pickle_deepcopy(obj): return pickle.loads(pickle.dumps(obj)) # 应用场景:复制包含嵌套引用的复杂配置 config = { 'database': { 'host': 'localhost', 'port': 5432, 'pool': { 'min_size': 5, 'max_size': 20 } }, 'logging': { 'handlers': ['file', 'console'], 'nested_refs': [] } } # 添加自引用 config['logging']['nested_refs'].append(config['database']) # 用 pickle 完成深拷贝 config_copy = pickle_deepcopy(config) print(config_copy['logging']['nested_refs'][0]['host']) # localhost print(config_copy is not config) # True print(config_copy['logging']['nested_refs'][0] is not config['database']) # True

7.4 常见陷阱与排查

问题 原因 解决方案
PicklingError: Can't pickle ... 尝试pickle不支持的类型(如lambda、生成器、内部类、文件句柄) 使用 __reduce__ 自定义序列化,或排除该类属性
AttributeError: module ... has no attribute ... 反序列化时找不到对应的类(模块路径变化、改名或删除) 确保类定义在反序列化环境中存在,或使用版本迁移脚本
ModuleNotFoundError: No module named ... 依赖的模块在反序列化环境中未安装 安装缺失模块,或使用 Unpickler.find_class 自定义解析
UnicodeDecodeError 或编码问题 用文本模式打开了pickle文件('r' 而不是 'rb') 始终使用二进制模式:'wb'/'rb'
反序列化后的对象状态异常 类属性(class variable)可能已被修改,或静态初始化未执行 使用 __setstate__ 确保正确的初始化顺序

7.5 知识体系总结

pickle核心知识图谱:

1. 四大函数:dumps/loads(字节对象)、dump/load(文件对象)

2. 协议版本:0~5,版本越高越高效,但兼容性随之下降

3. 序列化范围:几乎所有Python对象,但函数/类仅存引用而非实现

4. 自定义控制:__getstate__、__setstate__、__reduce_ex__ 三大钩子

5. 安全红线:永远不加载不可信pickle数据 —— 这是最核心的警告

6. 性能优化:高协议 + 压缩库(gzip/bz2/lzma)是标准组合

7. 工具链:pickletools 用于调试和分析pickle字节码

pickle模块是Python生态系统中不可或缺的基础设施。它在科学计算(NumPy/SciPy模型持久化)、机器学习(scikit-learn/PyTorch模型保存)、分布式计算(multiprocessing模块的进程间通信)等核心领域中发挥着关键作用。理解pickle的工作原理不仅有助于正确使用这些框架,更能帮助开发者在数据处理管道的设计中做出更明智的架构决策。

最重要的一点是始终牢记安全性。pickle的设计哲学是"灵活性优先于安全性",这使得它极其强大但也极其危险。在实际项目中,应当明确划定pickle的使用边界:只在完全可信的环境和数据源之间使用,对外部输入的数据永远优先考虑JSON等安全格式。