12.1. pickle
- Python对象序列化¶
源代码: Lib / pickle.py
pickle
模块实现了用于对Python对象结构进行序列化和反序列化的二进制协议。“Pickling”是将Python对象转换为字节流的过程,“unpickling”是反向操作,由此字节流二进制文件或字节对象)转换回对象结构。酸洗(和解胶)也称为“串联”,“编组”,[1]或“压平”;然而,为了避免混淆,这里使用的术语是“pickling”和“unpickling”。
警告
pickle
模块对于错误或恶意构造的数据不安全。切勿从不受信任或未经身份验证的来源接收数据。
12.1.1与其他Python模块的关系¶
12.1.1.1.与marshal
的比较¶
Python有一个更原始的序列化模块,称为marshal
,但是一般情况下pickle
应该始终是序列化Python对象的首选方法。marshal
主要用于支持Python的.pyc
文件。
pickle
模块会跟踪已经序列化的对象,因此以后对同一对象的引用将不会再次序列化。marshal
则不会这样做。这对于递归对象和对象共享都有影响。递归对象是包含对自身的引用的对象。这些不是元组处理,事实上,尝试封送递归对象会导致Python解释器崩溃。当在对象层次结构中的不同位置对同一对象进行多个引用被序列化时,将发生对象共享。
pickle
只存储此类对象一次,并确保所有其他引用指向主副本。共享对象保持共享,这对可变对象非常重要。marshal
不能用于序列化用户定义的类及其实例。pickle
可以透明地保存和恢复类实例,但是类定义必须是可导入的,并且存储在与存储对象时相同的模块中。marshal
序列化格式不能保证在Python版本之间可移植。由于其生命中的主要工作是支持.pyc
文件,Python实现者保留在需要时以非向后兼容的方式更改序列化格式的权利。pickle
序列化格式保证在Python版本之间向后兼容。
12.1.1.2.与json
的比较¶
在pickle协议和JSON(JavaScript对象表示法)之间存在根本区别:
- JSON是一种文本序列化格式(它输出unicode文本,虽然大多数时候它被编码为
utf-8
),而pickle是一个二进制序列化格式; - JSON是人类可读的,而pickle不是;
- JSON是可互操作的,并且在Python生态系统之外广泛使用,而pickle是特定于Python的;
- 默认情况下,JSON只能表示Python内建类型的一个子集,并且没有自定义类; pickle可以代表极大数量的Python类型(其中许多是通过巧妙地使用Python内省功能自动实现的;复杂的情况可以通过实现specific object APIs来解决)。
也可以看看
json
模块:允许JSON序列化和反序列化的标准库模块。
12.1.2.数据流格式¶
pickle
使用的数据格式是特定于Python的。这具有的优点是没有由诸如JSON或XDR(其不能表示指针共享)的外部标准强加的限制;然而这意味着非Python程序可能无法重建pickled Python对象。
默认情况下,pickle
数据格式使用相对紧凑的二进制表示。如果您需要最佳大小特征,则可以有效地compress pickled数据。
模块pickletools
包含用于分析pickle
生成的数据流的工具。pickletools
源代码对pickle协议使用的操作码有广泛的意见。
目前有5种不同的协议可以用于酸洗。使用的协议越高,更新的Python版本需要读取生产的泡菜。
- 协议版本0是原始的“人类可读”协议,并且向后兼容早期版本的Python。
- 协议版本1是一个旧的二进制格式,也与早期版本的Python兼容。
- 协议版本2在Python 2.3中引入。它提供new-style class更高效的酸洗。有关协议2带来的改进的信息,请参阅 PEP 307。
- 在Python 3.0中添加了协议版本3。它明确支持
bytes
对象,不能由Python 2.x取消绑定。这是默认协议,当与其他Python 3版本兼容时需要推荐的协议。 - 在Python 3.4中添加了协议版本4。它增加了对非常大的对象,pickling更多种类的对象和一些数据格式优化的支持。有关协议4带来的改进的信息,请参见 PEP 3154。
12.1.3.模块接口¶
要序列化对象层次结构,只需调用dumps()
函数。类似地,要解串行化数据流,您可以调用loads()
函数。但是,如果您想要更多地控制序列化和反序列化,您可以分别创建Pickler
或Unpickler
对象。
pickle
模块提供以下常量:
-
pickle.
HIGHEST_PROTOCOL
¶ 整数,最高的protocol version可用。此值可作为协议值传递给函数
dump()
和dumps()
以及Pickler
-
pickle.
DEFAULT_PROTOCOL
¶ 整数,默认的protocol version用于酸洗。可能小于
HIGHEST_PROTOCOL
。目前默认的协议是3,一个为Python 3设计的新协议。
pickle
模块提供以下功能,使酸洗过程更方便:
-
pickle.
dump
(obj, file, protocol=None, *, fix_imports=True)¶ 将obj的腌制表示写入打开的file object 文件。这相当于
Pickler(文件, 协议).dump(obj)
。可选的协议参数,一个整数,告诉pickler使用给定的协议;支持的协议为0到
HIGHEST_PROTOCOL
。如果未指定,默认值为DEFAULT_PROTOCOL
。如果指定了负数,则选择HIGHEST_PROTOCOL
。文件参数必须具有接受单个字节参数的write()方法。因此,它可以是为二进制写入打开的磁盘文件,
io.BytesIO
实例或满足此接口的任何其他自定义对象。如果fix_imports为true且protocol小于3,pickle将尝试将新的Python 3名称映射到Python 2中使用的旧模块名称,以便pickle数据流可以用Python 2读取。
-
pickle.
dumps
(obj, protocol=None, *, fix_imports=True)¶ 将对象的腌制表示作为
bytes
对象返回,而不是将其写入文件。参数protocol和fix_imports的含义与
dump()
中的含义相同。
-
pickle.
load
(file, *, fix_imports=True, encoding="ASCII", errors="strict")¶ 从打开的文件对象file读取pickled对象表示形式,并返回其中重新构建的对象层次结构。它等同于
Unpickler(file).load()
。Pickle协议的版本会自动检测,所以不需要protocol参数。超过腌制对象表示的字节将被忽略。
参数文件必须有两个方法,一个读取整数参数的read()方法和一个不需要参数的readline()方法。这两种方法都应该返回字节。因此,文件可以是为二进制读取打开的磁盘文件,
io.BytesIO
对象或满足此接口的任何其他自定义对象。可选的关键字参数是fix_imports,编码和错误,用于控制Python 2生成的pickle流的兼容性支持。如果fix_imports为true,pickle将尝试将旧的Python 2名称映射到Python 3中使用的新名称。编码和错误告诉pickle如何解码由Python 2 pickled的8位字符串实例;这些默认分别为'ASCII'和'strict'。编码可以是“bytes”来读取这些8位字符串实例作为字节对象。
-
pickle.
loads
(bytes_object, *, fix_imports=True, encoding="ASCII", errors="strict")¶ 从
bytes
对象读取腌制对象层次结构,并返回其中指定的重构对象层次结构。腌汁的协议版本被自动检测,因此不需要协议参数。超过腌制对象表示的字节将被忽略。
可选的关键字参数是fix_imports,编码和错误,用于控制Python 2生成的pickle流的兼容性支持。如果fix_imports为true,pickle将尝试将旧的Python 2名称映射到Python 3中使用的新名称。编码和错误告诉pickle如何解码由Python 2 pickled的8位字符串实例;这些默认分别为'ASCII'和'strict'。编码可以是“bytes”来读取这些8位字符串实例作为字节对象。
pickle
模块定义了三个例外:
- exception
pickle.
PicklingError
¶ Pickler
遇到不可拆分对象时引发错误。它继承PickleError
。请参阅What can be pickled and unpickled?以了解什么类型的对象可以腌制。
- exception
pickle.
UnpicklingError
¶ 在解除对象的取消(例如数据损坏或安全违规)时出现问题。它继承
PickleError
。注意,在解压缩期间也可以引发其他异常,包括(但不一定限于)AttributeError,EOFError,ImportError和IndexError。
pickle
模块导出两个类,Pickler
和Unpickler
:
- class
pickle.
Pickler
(file, protocol=None, *, fix_imports=True)¶ 这需要一个二进制文件来写一个pickle数据流。
可选的协议参数,一个整数,告诉pickler使用给定的协议;支持的协议为0到
HIGHEST_PROTOCOL
。如果未指定,默认值为DEFAULT_PROTOCOL
。如果指定了负数,则选择HIGHEST_PROTOCOL
。文件参数必须具有接受单个字节参数的write()方法。因此,它可以是为二进制写入打开的磁盘文件,
io.BytesIO
实例或满足此接口的任何其他自定义对象。如果fix_imports为true且protocol小于3,pickle将尝试将新的Python 3名称映射到Python 2中使用的旧模块名称,以便pickle数据流可以用Python 2读取。
-
dump
(obj)¶ 将obj的腌制表示写入构造函数中给出的打开文件对象。
-
persistent_id
(obj)¶ 默认情况下不执行任何操作。这存在,所以一个子类可以覆盖它。
如果
persistent_id()
返回None
,obj像往常一样被选中。任何其他值会导致Pickler
将返回的值作为obj的持久性标识发出。此持久性ID的含义应由Unpickler.persistent_load()
定义。请注意,persistent_id()
返回的值本身不能有持久性ID。有关使用的详细信息和示例,请参见Persistence of External Objects。
-
dispatch_table
¶ pickler对象的调度表是可以使用
copyreg.pickle()
声明的缩减函数的注册表。它是一个映射,其键是类并且其值是缩减函数。减少函数接受关联类的单个参数,并且应该符合与__reduce__()
方法相同的接口。By default, a pickler object will not have a
dispatch_table
attribute, and it will instead use the global dispatch table managed by thecopyreg
module. 但是,要自定义特定pickler对象的pickling,可以将dispatch_table
属性设置为类似dict的对象。或者,如果Pickler
的子类具有dispatch_table
属性,那么这将用作该类的实例的默认分派表。有关使用示例,请参见Dispatch Tables。
版本3.3中的新功能。
-
fast
¶ 已弃用。如果设置为true值,请启用快速模式。快速模式禁用备忘录的使用,因此通过不生成多余的PUT操作码来加速酸洗过程。它不应该与自引用对象一起使用,否则会导致
Pickler
无限递归。如果您需要更多的小型泡菜,请使用
pickletools.optimize()
。
-
- class
pickle.
Unpickler
(file, *, fix_imports=True, encoding="ASCII", errors="strict")¶ 这需要一个二进制文件来读取pickle数据流。
腌汁的协议版本被自动检测,因此不需要协议参数。
参数文件必须有两个方法,一个读取整数参数的read()方法和一个不需要参数的readline()方法。这两种方法都应该返回字节。因此,文件可以是为二进制读取打开的磁盘文件对象,
io.BytesIO
对象或满足此接口的任何其他自定义对象。可选的关键字参数是fix_imports,编码和错误,用于控制Python 2生成的pickle流的兼容性支持。如果fix_imports为true,pickle将尝试将旧的Python 2名称映射到Python 3中使用的新名称。编码和错误告诉pickle如何解码由Python 2 pickled的8位字符串实例;这些默认分别为'ASCII'和'strict'。编码可以是“bytes”,以将这些ß8位字符串实例读取为字节对象。
-
load
()¶ 从构造函数中给出的打开文件对象中读取一个腌制对象表示,并返回其中指定的重构对象层次结构。超过腌制对象表示的字节将被忽略。
-
persistent_load
(pid)¶ 默认情况下引发
UnpicklingError
。如果已定义,
persistent_load()
应返回持久性标识pid指定的对象。如果遇到无效的持久性ID,应提出UnpicklingError
。有关使用的详细信息和示例,请参见Persistence of External Objects。
-
find_class
(module, name)¶ 如有必要,请输入模块,并返回名称的对象,其中模块和名称参数
str
对象。注意,与其名称不同,find_class()
也用于查找函数。子类可以覆盖此类,以获得对什么类型的对象以及如何加载它们的控制,从而潜在地降低安全风险。有关详细信息,请参阅Restricting Globals。
-
12.1.4.什么可以pickled和unpickled?¶
以下类型可以选择:
None
,True
, andFalse
- 整数,浮点数,复数
- 字符串,字节,字节数
- 元组,列表,集合和仅包含可拾取对象的字典
- 在模块顶层定义的函数(使用
def
,而不是lambda
) - 内建函数定义在模块的顶层
- 在模块的顶层定义的类
- 其类
__dict__
或调用__getstate__()
的类的实例是可选的(有关详细信息,请参阅Pickling Class Instances一节)。
尝试pickle unpicklable对象将引发PicklingError
异常;当发生这种情况时,未指定数量的字节可能已经被写入底层文件。尝试挑选高度递归的数据结构可能会超过最大递归深度,在这种情况下会出现RecursionError
。您可以使用sys.setrecursionlimit()
仔细引用此限制。
注意,函数(内建和用户定义)由“完全限定”名称引用而不是值。[2]这意味着只有函数名称被pickled,以及函数定义的模块的名称。函数的代码,或者它的任何函数属性都没有被腌制。因此,定义模块必须在unpickling环境中是可导入的,并且模块必须包含命名对象,否则将引发异常。[3]
类似地,类通过命名引用进行选择,因此在取消取消环境中应用相同的限制。请注意,没有类的代码或数据被pickle,因此在下面的示例中,类属性attr
不会在unpickling环境中恢复:
class Foo:
attr = 'A class attribute'
picklestring = pickle.dumps(Foo)
这些限制是为什么picklable函数和类必须在模块的顶层定义。
类似地,当类实例被pickled时,它们的类的代码和数据不与它们一起被腌制。只有实例数据被酸洗。这是有意的,所以你可以修复类中的错误或添加方法到类,并仍然加载的对象,使用早期版本的类创建的。如果你计划有长生命的对象将看到一个类的许多版本,可能值得把一个版本号放在对象中,以便可以通过类的__setstate__()
方法。
12.1.5.pickling类实例¶
在本节中,我们将描述可用于定义,自定义和控制类实例如何被pickled和unpickled的一般机制。
在大多数情况下,不需要额外的代码来使实例可拾取。默认情况下,pickle将通过内省检索实例的类和属性。当类实例取消取消时,其__init__()
方法通常不被调用。默认行为首先创建未初始化的实例,然后恢复保存的属性。以下代码显示了此行为的实现:
def save(obj):
return (obj.__class__, obj.__dict__)
def load(cls, attributes):
obj = cls.__new__(cls)
obj.__dict__.update(attributes)
return obj
类可以通过提供一个或多个特殊方法来更改默认行为:
-
object.
__getnewargs_ex__
()¶ 在协议4和更新版本中,实现
__getnewargs_ex__()
方法的类可以规定在取消发送时传递给__new__()
方法的值。该方法必须返回一对(args, kwargs)
其中args是位置参数的元组, kwargs用于构造对象的命名参数的字典。这些将在解压缩后传递给__new__()
方法。如果类的
__new__()
方法需要仅限关键字的参数,则应实现此方法。否则,建议兼容性实现__getnewargs__()
。
-
object.
__getnewargs__
()¶ 此方法的作用与
__getnewargs_ex__()
类似,但适用于协议2和更高版本。它必须返回一个参数args
的元组,它将在取消发送时传递给__new__()
方法。在协议4和更新版本中,如果定义
__getnewargs_ex__()
,则不会调用__getnewargs__()
-
object.
__getstate__
()¶ 类可以进一步影响他们的实例如何被酸洗;如果类定义了方法
__getstate__()
,它被调用,返回的对象被作为实例的内容而不是实例的字典的内容。如果__getstate__()
方法不存在,实例的__dict__
将像往常一样被腌制。
-
object.
__setstate__
(state)¶ 取消绑定后,如果类定义
__setstate__()
,则以unpickled状态调用。在这种情况下,不需要状态对象是字典。否则,腌制状态必须是字典,其项目被分配给新实例的字典。注意
如果
__getstate__()
返回一个假值,则在取消冻结时不会调用__setstate__()
方法。
有关如何使用方法__getstate__()
和__setstate__()
的详细信息,请参阅Handling Stateful Objects
注意
在取消时间,可以在实例上调用诸如__getattr__()
,__getattribute__()
或__setattr__()
如果这些方法依赖于一些内部不变量为真,则类型应该实现__getnewargs__()
或__getnewargs_ex__()
以建立这样的不变量;否则,将不会调用__new__()
或__init__()
。
正如我们将看到的,pickle不直接使用上述方法。实际上,这些方法是实现__reduce__()
特殊方法的复制协议的一部分。复制协议提供用于检索对对象进行酸洗和复制所必需的数据的统一接口。[4]
虽然功能强大,但直接在类中实现__reduce__()
是容易出错的。为此,类设计器应该使用高级接口(即__getnewargs_ex__()
,__getstate__()
和__setstate__()
)可能。然而,我们将展示使用__reduce__()
是唯一选项,或者导致更有效的酸洗或两者兼而有之的情况。
-
object.
__reduce__
()¶ 该接口当前定义如下。
__reduce__()
方法不带参数,应返回字符串或最好是一个元组(返回的对象通常称为“减少值”)。如果返回一个字符串,该字符串应该被解释为全局变量的名称。它应该是对象相对于其模块的本地名称; pickle模块搜索模块命名空间以确定对象的模块。此行为通常对单例有用。
当返回一个元组时,它必须在两到五个项之间。可以省略可选项,也可以提供
None
作为其值。每个项目的语义是按顺序:- 将被调用以创建对象的初始版本的可调用对象。
- 可调用对象的参数的元组。如果callable不接受任何参数,则必须给出一个空元组。
- 可选地,对象的状态将被传递到对象的
__setstate__()
方法,如前所述。如果对象没有这样的方法,那么值必须是字典,并且它将被添加到对象的__dict__
属性。 - 可选地,迭代器(而不是序列)产生连续项。这些项将使用
obj.append(item)
或批量使用obj.extend(list_of_items)
附加到对象。这主要用于列表子类,但可以由其他类使用,只要它们具有适当的声明的append()
和extend()
方法。(是否使用append()
或extend()
取决于使用的pickle协议版本以及要附加的项目数,因此必须支持两者。 - 可选地,产生连续键值对的迭代器(而不是序列)。这些项将使用
obj [key] = 值
存储到对象。这主要用于字典子类,但可以由其他类使用,只要它们实现__setitem__()
。
-
object.
__reduce_ex__
(protocol)¶ 或者,可以定义
__reduce_ex__()
方法。唯一的区别是这个方法应该采用单个整数参数,即协议版本。当定义时,pickle将优先于__reduce__()
方法。此外,__reduce__()
自动成为扩展版本的同义词。此方法的主要用途是为较早的Python版本提供向后兼容的reduce值。
12.1.5.1.外部对象的持久性¶
为了对象持久化的益处,pickle
模块支持对经过腌制的数据流外部的对象的引用的概念。这样的对象由持久性ID引用,持久性ID应为字母数字字符(对于协议0)[5]的字符串,或者只是任意对象(对于任何较新的协议)。
这种持久性ID的解析不是由pickle
模块定义的;它将分别将此解决方案委托给pickler和unpickler上的用户定义的方法persistent_id()
和persistent_load()
。
要选择具有外部持久性标识的对象,pickler必须有一个自定义的persistent_id()
方法,它将一个对象作为参数,并返回None
那个对象。当返回None
时,pickler只是像正常一样腌制对象。当返回持久性ID字符串时,pickler将拾取该对象以及一个标记,以便unpickler将它识别为持久性ID。
要取消取消外部对象,取消管理器必须有一个自定义persistent_load()
方法,该方法需要持久的ID对象并返回引用的对象。
这里是一个综合示例,介绍如何使用持久性标识来引用外部对象。
# Simple example presenting how persistent ID can be used to pickle
# external objects by reference.
import pickle
import sqlite3
from collections import namedtuple
# Simple class representing a record in our database.
MemoRecord = namedtuple("MemoRecord", "key, task")
class DBPickler(pickle.Pickler):
def persistent_id(self, obj):
# Instead of pickling MemoRecord as a regular class instance, we emit a
# persistent ID.
if isinstance(obj, MemoRecord):
# Here, our persistent ID is simply a tuple, containing a tag and a
# key, which refers to a specific record in the database.
return ("MemoRecord", obj.key)
else:
# If obj does not have a persistent ID, return None. This means obj
# needs to be pickled as usual.
return None
class DBUnpickler(pickle.Unpickler):
def __init__(self, file, connection):
super().__init__(file)
self.connection = connection
def persistent_load(self, pid):
# This method is invoked whenever a persistent ID is encountered.
# Here, pid is the tuple returned by DBPickler.
cursor = self.connection.cursor()
type_tag, key_id = pid
if type_tag == "MemoRecord":
# Fetch the referenced record from the database and return it.
cursor.execute("SELECT * FROM memos WHERE key=?", (str(key_id),))
key, task = cursor.fetchone()
return MemoRecord(key, task)
else:
# Always raises an error if you cannot return the correct object.
# Otherwise, the unpickler will think None is the object referenced
# by the persistent ID.
raise pickle.UnpicklingError("unsupported persistent object")
def main():
import io
import pprint
# Initialize and populate our database.
conn = sqlite3.connect(":memory:")
cursor = conn.cursor()
cursor.execute("CREATE TABLE memos(key INTEGER PRIMARY KEY, task TEXT)")
tasks = (
'give food to fish',
'prepare group meeting',
'fight with a zebra',
)
for task in tasks:
cursor.execute("INSERT INTO memos VALUES(NULL, ?)", (task,))
# Fetch the records to be pickled.
cursor.execute("SELECT * FROM memos")
memos = [MemoRecord(key, task) for key, task in cursor]
# Save the records using our custom DBPickler.
file = io.BytesIO()
DBPickler(file).dump(memos)
print("Pickled records:")
pprint.pprint(memos)
# Update a record, just for good measure.
cursor.execute("UPDATE memos SET task='learn italian' WHERE key=1")
# Load the records from the pickle data stream.
file.seek(0)
memos = DBUnpickler(file, conn).load()
print("Unpickled records:")
pprint.pprint(memos)
if __name__ == '__main__':
main()
12.1.5.2.Dispatch Tables¶
如果想要定制一些类的pickling而不干扰依赖于pickling的任何其他代码,那么可以创建一个带有私有调度表的pickler。
由copyreg
模块管理的全局分派表可用作copyreg.dispatch_table
。因此,可以选择使用copyreg.dispatch_table
的修改副本作为专用分派表。
例如
f = io.BytesIO()
p = pickle.Pickler(f)
p.dispatch_table = copyreg.dispatch_table.copy()
p.dispatch_table[SomeClass] = reduce_SomeClass
使用专用处理SomeClass
类的专用分派表创建pickle.Pickler
的实例。或者,代码
class MyPickler(pickle.Pickler):
dispatch_table = copyreg.dispatch_table.copy()
dispatch_table[SomeClass] = reduce_SomeClass
f = io.BytesIO()
p = MyPickler(f)
但是所有实例MyPickler
将默认共享相同的分派表。使用copyreg
模块的等效代码为
copyreg.pickle(SomeClass, reduce_SomeClass)
f = io.BytesIO()
p = pickle.Pickler(f)
12.1.5.3.处理状态对象¶
这里有一个例子演示如何修改类的酸洗行为。TextReader
类打开一个文本文件,并在每次调用readline()
方法时返回行号和行内容。如果TextReader
实例被选中,除了文件对象成员的所有属性都被保存。当实例取消选中时,文件将重新打开,并从最后一个位置继续读取。__setstate__()
和__getstate__()
方法用于实现此行为。
class TextReader:
"""Print and number lines in a text file."""
def __init__(self, filename):
self.filename = filename
self.file = open(filename)
self.lineno = 0
def readline(self):
self.lineno += 1
line = self.file.readline()
if not line:
return None
if line.endswith('\n'):
line = line[:-1]
return "%i: %s" % (self.lineno, line)
def __getstate__(self):
# Copy the object's state from self.__dict__ which contains
# all our instance attributes. Always use the dict.copy()
# method to avoid modifying the original state.
state = self.__dict__.copy()
# Remove the unpicklable entries.
del state['file']
return state
def __setstate__(self, state):
# Restore instance attributes (i.e., filename and lineno).
self.__dict__.update(state)
# Restore the previously opened file's state. To do so, we need to
# reopen it and read from it until the line count is restored.
file = open(self.filename)
for _ in range(self.lineno):
file.readline()
# Finally, save the file.
self.file = file
示例用法可能如下所示:
>>> reader = TextReader("hello.txt")
>>> reader.readline()
'1: Hello world!'
>>> reader.readline()
'2: I am line number two.'
>>> new_reader = pickle.loads(pickle.dumps(reader))
>>> new_reader.readline()
'3: Goodbye!'
12.1.6.限制全局变量¶
默认情况下,unpickling将导入它在pickle数据中找到的任何类或函数。对于许多应用程序,此行为是不可接受的,因为它允许unpickler导入和调用任意代码。只考虑这个手工制作的pickle数据流加载时:
>>> import pickle
>>> pickle.loads(b"cos\nsystem\n(S'echo hello world'\ntR.")
hello world
0
在此示例中,unpickler导入os.system()
函数,然后应用字符串参数“echo hello world”。虽然这个例子是不好的,但不难想象会损坏你的系统。
因此,您可以通过自定义Unpickler.find_class()
来控制取消取消取消的内容。与其名称不同的是,当请求全局(即类或函数)时,调用Unpickler.find_class()
。因此,可以完全禁止全局变量或将其限制为安全子集。
这里有一个unpickler的例子,只允许加载来自builtins
的几个安全类:
import builtins
import io
import pickle
safe_builtins = {
'range',
'complex',
'set',
'frozenset',
'slice',
}
class RestrictedUnpickler(pickle.Unpickler):
def find_class(self, module, name):
# Only allow safe classes from builtins.
if module == "builtins" and name in safe_builtins:
return getattr(builtins, name)
# Forbid everything else.
raise pickle.UnpicklingError("global '%s.%s' is forbidden" %
(module, name))
def restricted_loads(s):
"""Helper function analogous to pickle.loads()."""
return RestrictedUnpickler(io.BytesIO(s)).load()
我们的unpickler工作的样例用法:
>>> restricted_loads(pickle.dumps([1, 2, range(15)]))
[1, 2, range(0, 15)]
>>> restricted_loads(b"cos\nsystem\n(S'echo hello world'\ntR.")
Traceback (most recent call last):
...
pickle.UnpicklingError: global 'os.system' is forbidden
>>> restricted_loads(b'cbuiltins\neval\n'
... b'(S\'getattr(__import__("os"), "system")'
... b'("echo hello world")\'\ntR.')
Traceback (most recent call last):
...
pickle.UnpicklingError: global 'builtins.eval' is forbidden
正如我们的例子所示,你必须小心你允许被解冻。因此,如果担心安全问题,您可能需要考虑替代方案,例如xmlrpc.client
中的编组API或第三方解决方案。
12.1.8.示例¶
import pickle
# An arbitrary collection of objects supported by pickle.
data = {
'a': [1, 2.0, 3, 4+6j],
'b': ("character string", b"byte string"),
'c': {None, True, False}
}
with open('data.pickle', 'wb') as f:
# Pickle the 'data' dictionary using the highest protocol available.
pickle.dump(data, f, pickle.HIGHEST_PROTOCOL)
以下示例读取生成的pickled数据。
import pickle
with open('data.pickle', 'rb') as f:
# The protocol version used is detected automatically, so we do not
# have to specify it.
data = pickle.load(f)
也可以看看
脚注
[1] | 不要将此与marshal 模块混淆 |
[2] | 这就是为什么lambda 函数不能被pickled:所有lambda 函数共享同一个名称:<lambda> 。 |
[3] | 引发的异常可能是ImportError 或AttributeError ,但可能是其他原因。 |
[4] | copy 模块使用此协议进行浅层和深层复制操作。 |
[5] | 对字母数字字符的限制是由于事实,协议0中的持久性ID由换行符字符分隔。因此,如果在持久性ID中出现任何类型的换行符,则生成的pickle将变得不可读。 |