pickle-Python对象序列化(1)pickle和marshal模块永久存储Python数据(必读进阶Python教程)(参考资料)
该pickle
模块实现了用于序列化和反序列化Python对象结构的二进制协议。 “Pickling”是将Python对象层次结构转换为字节流的过程, “unpickling”是反向操作,从而将字节流(来自二进制文件或类似字节的对象)转换回对象层次结构。酸洗(和去除)也可称为“序列化”,“编组”,[1]或“扁平化”; 但是,为了避免混淆,这里使用的术语是“酸洗”和“破坏”。
警告
该pickle
模块对于错误或恶意构造的数据是不安全的。切勿取消从不受信任或未经身份验证的来源收到的数据。
与其他Python模块的关系
与比较marshal
Python有一个更原始的序列化模块marshal
,但通常pickle
应该始终是序列化Python对象的首选方法。 marshal
主要用于支持Python的.pyc
文件。
该pickle
模块与以下marshal
几个重要方面不同:
-
该
pickle
模块会跟踪它已经序列化的对象,以便以后对同一对象的引用不会再次序列化。marshal
不这样做。这对递归对象和对象共享都有影响。递归对象是包含对自身的引用的对象。这些不是由marshal处理的,事实上,试图编组递归对象会使Python解释器崩溃。当序列化的对象层次结构中的不同位置有多个对同一对象的引用时,就会发生对象共享。
pickle
仅存储此类对象一次,并确保所有其他引用指向主副本。共享对象保持共享,这对于可变对象非常重要。 -
marshal
不能用于序列化用户定义的类及其实例。pickle
可以透明地保存和恢复类实例,但是类定义必须是可导入的,并且与存储对象时存在于同一模块中。 -
该
marshal
序列化格式是不能保证整个Python版本移植。因为它的主要工作是支持.pyc
文件,所以Python实现者保留在需要时以非向后兼容的方式更改序列化格式的权利。的pickle
串行化格式被保证是向后兼容的不同的Python版本提供一个兼容pickle协议被选择并与Python 2封装状态代码的交易到Python 3类型的差别,如果你的数据被跨越该唯一重大更改语言边界。
与比较json
pickle协议和JSON(JavaScript Object Notation)之间存在根本区别 :
- JSON是一种文本序列化格式(它输出unicode文本,虽然大部分时间它被编码
utf-8
),而pickle是二进制序列化格式; - JSON是人类可读的,而pickle则不是;
- JSON是可互操作的,并且在Python生态系统之外广泛使用,而pickle是特定于Python的;
- 默认情况下,JSON只能表示Python内置类型的子集,而不能表示自定义类; pickle可以表示极其庞大的Python类型(其中许多是自动的,通过巧妙地使用Python的内省工具;复杂的案例可以通过实现特定的对象API来解决)。
也可以看看
该json
模块:标准库模块,允许JSON序列化和反序列化。
数据流格式
使用的数据格式pickle
是特定于Python的。这样做的优点是没有外部标准强加的限制,例如JSON或XDR(不能代表指针共享); 但这意味着非Python程序可能无法重建pickled Python对象。
默认情况下,pickle
数据格式使用相对紧凑的二进制表示。如果您需要最佳尺寸特征,则可以有效地 压缩腌制数据。
该模块pickletools
包含用于分析生成的数据流的工具pickle
。 pickletools
源代码对pickle协议使用的操作码有广泛的评论。
目前有5种不同的方案可用于酸洗。协议使用的越高,读取生成的pickle所需的Python版本就越新。
- 协议版本0是原始的“人类可读”协议,并且向后兼容早期版本的Python。
- 协议版本1是旧的二进制格式,它也与早期版本的Python兼容。
- 在Python 2.3中引入了协议版本2。它提供了更有效的新式类型的酸洗。参考PEP 307获取有关协议2带来的改进的信息。
- 在Python 3.0中添加了协议版本3。它具有对
bytes
对象的显式支持, 并且不能被Python 2.x打开。这是默认协议,需要与其他Python 3版本兼容时的推荐协议。 - 在Python 3.4中添加了协议版本4。它增加了对非常大的对象的支持,挑选更多种类的对象,以及一些数据格式优化。参考PEP 3154获取有关协议4带来的改进的信息。
注意
序列化是一种比持久性更原始的概念; 虽然 pickle
读取和写入文件对象,但它不处理命名持久对象的问题,也不处理对持久对象的并发访问(甚至更复杂)的问题。该pickle
模块可以将一个复杂的对象成字节流,它可以改造字节流成一个对象具有相同的内部结构。对这些字节流最明显的做法可能是将它们写入文件,但也可以想象它们通过网络发送或将它们存储在数据库中。该shelve
模块提供了一个简单的界面,用于在DBM样式的数据库文件上pickle和unpickle对象。
模块接口
要序列化对象层次结构,只需调用该dumps()
函数即可。同样,要对数据流进行反序列化,请调用该loads()
函数。但是,如果要对序列化和反序列化进行更多控制,可以分别创建一个Pickler
或一个Unpickler
对象。
该pickle
模块提供以下常量:
pickle.
DEFAULT_PROTOCOL
- 整数,用于酸洗的默认协议版本。可能不到
HIGHEST_PROTOCOL
。目前,默认协议是3,这是为Python 3设计的新协议。
该pickle
模块提供以下功能,使酸洗过程更加方便:
pickle.
dump
(obj,file,protocol = None,*,fix_imports = True )- 将obj的pickled表示写入打开的文件对象 文件。这相当于。
Pickler(file, protocol).dump(obj)
可选的协议参数,一个整数,告诉pickler使用给定的协议; 支持的协议是0到
HIGHEST_PROTOCOL
。如果未指定,则默认为DEFAULT_PROTOCOL
。如果指定了负数,HIGHEST_PROTOCOL
则选择。的文件参数必须具有接受单个字节的参数写()方法。因此,它可以是为二进制写入打开的磁盘文件,
io.BytesIO
实例或满足此接口的任何其他自定义对象。如果fix_imports为true且protocol小于3,则pickle将尝试将新的Python 3名称映射到Python 2中使用的旧模块名称,以便使用Python 2可读取pickle数据流。
pickle.
dumps
(obj,protocol = None,*,fix_imports = True )- 将对象的pickled表示作为
bytes
对象返回,而不是将其写入文件。参数protocol和fix_imports具有与in中相同的含义
dump()
。
pickle.
load
(file,*,fix_imports = True,encoding =“ASCII”,errors =“strict” )- 从打开的文件对象 文件中读取pickle对象表示,并返回其中指定的重构对象层次结构。这相当于
Unpickler(file).load()
。pickle的协议版本是自动检测的,因此不需要协议参数。超过pickle对象的表示的字节将被忽略。
参数文件必须有两个方法,一个采用整数参数的read()方法和一个不需要参数的readline()方法。两种方法都应返回字节。因此,文件可以是为二进制读取而打开的磁盘文件,
io.BytesIO
对象或满足此接口的任何其他自定义对象。可选的关键字参数是fix_imports,encoding和errors,用于控制Python 2生成的pickle流的兼容性支持。如果fix_imports为true,则pickle将尝试将旧的Python 2名称映射到Python 3中使用的新名称。编码和 错误告诉pickle如何解码Python 2腌制的8位字符串实例; 这些默认分别为’ASCII’和’strict’。该编码可以是“字节”作为字节对象读取这些8位串的实例。使用
encoding='latin1'
所需的取储存NumPy的阵列和实例datetime
,date
并且time
被Python 2酸洗。
pickle.
loads
(bytes_object,*,fix_imports = True,encoding =“ASCII”,errors =“strict” )- 从
bytes
对象读取pickle对象层次结构并返回其中指定的重构对象层次结构。pickle的协议版本是自动检测的,因此不需要协议参数。超过pickle对象的表示的字节将被忽略。
可选的关键字参数是fix_imports,encoding和errors,用于控制Python 2生成的pickle流的兼容性支持。如果fix_imports为true,则pickle将尝试将旧的Python 2名称映射到Python 3中使用的新名称。编码和 错误告诉pickle如何解码Python 2腌制的8位字符串实例; 这些默认分别为’ASCII’和’strict’。该编码可以是“字节”作为字节对象读取这些8位串的实例。使用
encoding='latin1'
所需的取储存NumPy的阵列和实例datetime
,date
并且time
被Python 2酸洗。
该pickle
模块定义了三个例外:
- 异常
pickle.
PickleError
- 其他酸洗异常的通用基类。它继承了
Exception
。
- 异常
pickle.
PicklingError
- 遇到不可解析的对象时引发错误
Pickler
。它继承了PickleError
。请参阅什么可以酸洗和去除?了解哪些物体可以腌制。
- 异常
pickle.
UnpicklingError
- 解开对象时出现问题(例如数据损坏或安全违规)时出错。它继承了
PickleError
。请注意,在unpickling期间也可能会引发其他异常,包括(但不一定限于)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
则选择。的文件参数必须具有接受单个字节的参数写()方法。因此,它可以是为二进制写入打开的磁盘文件,
io.BytesIO
实例或满足此接口的任何其他自定义对象。如果fix_imports为true且protocol小于3,则pickle将尝试将新的Python 3名称映射到Python 2中使用的旧模块名称,以便使用Python 2可读取pickle数据流。
dump
(obj )- 将obj的pickled表示写入构造函数中给出的打开文件对象。
persistent_id
(obj )- 默认情况下不做任何事 这是存在的,因此子类可以覆盖它。
如果
persistent_id()
返回None
,obj像往常一样被腌制。任何其他值都会导致Pickler
返回的值作为obj的持久ID 。这个持久性ID的含义应该由Unpickler.persistent_load()
。请注意,返回的值persistent_id()
本身不能具有持久ID。有关详细信息和使用示例,请参阅外部对象的持久性。
dispatch_table
- pickler对象的dispatch表是可以使用声明的减少函数的 注册表
copyreg.pickle()
。它是一个映射,其键是类,其值是缩减函数。reduction函数接受关联类的单个参数,并且应该与__reduce__()
方法一致。默认情况下,pickler对象不具有
dispatch_table
属性,而是使用copyreg
模块管理的全局调度表。但是,要为特定的pickler对象自定义pickle,可以将该dispatch_table
属性设置为类似dict的对象。或者,如果子类Pickler
具有dispatch_table
属性,那么这将用作该类实例的默认调度表。有关用法示例,请参阅Dispatch Tables。
版本3.3中的新功能。
fast
- 已过时。如果设置为真值,则启用快速模式。快速模式禁用备忘录的使用,因此通过不生成多余的PUT操作码来加速酸洗过程。它不应该与自引用对象一起使用,否则会导致
Pickler
无限递归。pickletools.optimize()
如果您需要更紧凑的泡菜,请使用。
- class
pickle.
Unpickler
(file,*,fix_imports = True,encoding =“ASCII”,errors =“strict” ) - 这需要一个二进制文件来读取pickle数据流。
pickle的协议版本是自动检测的,因此不需要协议参数。
参数文件必须有两个方法,一个采用整数参数的read()方法和一个不需要参数的readline()方法。两种方法都应返回字节。因此,文件可以是为二进制读取而打开的磁盘上文件对象,
io.BytesIO
对象或满足此接口的任何其他自定义对象。可选的关键字参数是fix_imports,encoding和errors,用于控制Python 2生成的pickle流的兼容性支持。如果fix_imports为true,则pickle将尝试将旧的Python 2名称映射到Python 3中使用的新名称。编码和 错误告诉pickle如何解码Python 2腌制的8位字符串实例; 这些默认分别为’ASCII’和’strict’。该编码可以是“字节”作为字节对象读取这些8位串的实例。
load
()- 从构造函数中给出的打开文件对象中读取pickle对象表示,并返回其中指定的重构对象层次结构。超过pickle对象的表示的字节将被忽略。
persistent_load
(pid )UnpicklingError
默认情况下提升。如果已定义,
persistent_load()
则应返回持久性ID pid指定的对象。如果遇到无效的持久性ID,则UnpicklingError
应该引发。有关详细信息和使用示例,请参阅外部对象的持久性。
find_class
(模块,名称)- 必要时导入模块并从中返回名为name的对象,其中module和name参数是
str
对象。注意,与其名称不同,find_class()
它也用于查找功能。子类可以覆盖它以控制对象的类型以及如何加载它们,从而可能降低安全风险。有关详细信息,请参阅限制全局。
什么可以腌制和去除?
可以腌制以下类型:
None
,True
和False
- 整数,浮点数,复数
- 字符串,字节,字节数组
- 仅包含可选对象的元组,列表,集和词典
- 在模块的顶层定义的函数(使用
def
,不是lambda
) - 在模块顶层定义的内置函数
- 在模块顶层定义的类
- 此类的实例
__dict__
或其调用结果__getstate__()
是可选择的(有关详细信息,请参阅“ 修补类实例”一节)。
尝试腌制不可摧毁的对象会引发PicklingError
异常; 当发生这种情况时,可能已经将未指定数量的字节写入底层文件。尝试挑选高度递归的数据结构可能会超过最大递归深度,RecursionError
在这种情况下会引发一个。你可以小心地提高这个限制 sys.setrecursionlimit()
。
请注意,函数(内置和用户定义)由“完全限定”的名称引用而非值引用。[2] 这意味着只有函数名称被腌制,以及定义函数的模块的名称。函数的代码或其任何函数属性都不会被pickle。因此,定义模块必须可以在unpickling环境中导入,并且模块必须包含命名对象,否则将引发异常。[3]
类似地,类通过命名引用进行pickle,因此适用于unpickling环境中的相同限制。请注意,不会对类的代码或数据进行pickle,因此在以下示例attr
中,在unpickling环境中不会还原class属性:
class Foo:
attr = 'A class attribute'
picklestring = pickle.dumps(Foo)
这些限制是必须在模块的顶层定义可选函数和类的原因。
类似地,当类实例被pickle时,它们的类的代码和数据不会与它们一起被pickle。仅腌制实例数据。这是故意完成的,因此您可以修复类中的错误或向类添加方法,并仍然加载使用该类的早期版本创建的对象。如果您计划拥有可以看到类的许多版本的长寿命对象,那么在对象中放置版本号可能是值得的,这样可以通过类的__setstate__()
方法进行适当的转换。
酸洗类实例
在本节中,我们将介绍可用于定义,自定义和控制类实例如何被pickle和unpickled的一般机制。
在大多数情况下,不需要额外的代码来使实例可选。默认情况下,pickle将通过内省检索实例的类和属性。在对类实例进行unpickled时,__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__
()- 在协议2和更新版本中,实现该
__getnewargs_ex__()
方法的类 可以指定__new__()
在取消排序时传递给该方法的值 。该方法必须返回一对 ,其中args是位置参数的元组,并且kwargs是用于构造对象的命名参数的字典。这些将在unpickling时传递给方法。(args, kwargs)
__new__()
如果
__new__()
类的方法需要仅关键字参数,则应实现此方法。否则,建议实现兼容性__getnewargs__()
。版本3.6中已更改:
__getnewargs_ex__()
现在在协议2和3中使用。
object.
__getnewargs__
()- 此方法的用途与此类似
__getnewargs_ex__()
,但仅支持位置参数。它必须返回一个参数元组,这些参数args
将__new__()
在unpickling时传递给方法。__getnewargs__()
如果__getnewargs_ex__()
已定义,则不会被调用。版本3.6中更改:在Python 3.6之前
__getnewargs__()
调用而不是__getnewargs_ex__()
在协议2和3中调用 。
object.
__getstate__
()- 类可以进一步影响他们的实例被腌制的方式; 如果类定义了方法
__getstate__()
,则调用它,并将返回的对象作为实例的内容进行pickle,而不是实例的字典的内容。如果该__getstate__()
方法不存在,__dict__
则像往常一样对实例进行pickle。
object.
__setstate__
(状态)- 在unpickling时,如果类定义
__setstate__()
,则以unpickled状态调用它。在这种情况下,不要求状态对象是字典。否则,pickle状态必须是字典,并且其项目将分配给新实例的字典。注意
如果
__getstate__()
返回false值,则__setstate__()
在unpickling时不会调用该方法。
有关如何使用方法和的更多信息,请参阅处理有状态对象一节。__getstate__()
__setstate__()
注意
在在unpickle时,一些方法,如__getattr__()
, __getattribute__()
或__setattr__()
可在该实例调用。如果这些方法依赖于某些内部不变量为真,则该类型应该实现__getnewargs__()
或 __getnewargs_ex__()
建立这样的不变量; 否则,既__new__()
不会也__init__()
不会被召唤。
正如我们将要看到的,pickle不直接使用上述方法。实际上,这些方法是实现__reduce__()
特殊方法的复制协议的一部分 。复制协议提供统一的界面,用于检索酸洗和复制对象所需的数据。[4]
虽然功能强大,但__reduce__()
直接在您的类中实现是容易出错的。因此,类设计者应尽可能使用高级接口(即__getnewargs_ex__()
,__getstate__()
和 __setstate__()
)。但是,我们将展示使用__reduce__()
是唯一的选择或导致更有效的酸洗或两者兼而有之的情况。
object.
__reduce__
()- 该接口目前定义如下。该
__reduce__()
方法不带参数,并且应返回字符串或最好返回元组(返回的对象通常称为“reduce value”)。如果返回字符串,则应将该字符串解释为全局变量的名称。它应该是对象相对于其模块的本地名称; pickle模块搜索模块名称空间以确定对象的模块。此行为通常对单身人士有用。
返回元组时,它必须介于两到五个项目之间。可选项可以省略,
None
也可以作为其值提供。每个项目的语义都是有序的:- 将调用的可调用对象,用于创建对象的初始版本。
- 可调用对象的参数元组。如果callable不接受任何参数,则必须给出一个空元组。
- 可选地,对象的状态,将
__setstate__()
如前所述传递给对象的 方法。如果对象没有这样的方法,则该值必须是字典,并且它将被添加到对象的__dict__
属性中。 - 可选地,迭代器(而不是序列)产生连续的项目。这些项目将使用
obj.append(item)
或批量使用附加到对象obj.extend(list_of_items)
。这主要是用于列表的子类,但可以通过其他类,只要他们使用append()
,并extend()
用适当的签名方法。(无论append()
或extend()
使用取决于哪泡菜协议版本被用作以及项目追加的次数,所以两者都必须被支持。) - 可选地,迭代器(不是序列)产生连续的键值对。这些项目将使用存储到对象。这主要用于字典子类,但只要它们实现,就可以被其他类使用。
obj[key] = value
__setitem__()
object.
__reduce_ex__
(协议)- 或者,
__reduce_ex__()
可以定义方法。唯一的区别是这个方法应该采用单个整数参数,即协议版本。在定义时,pickle将优先于该__reduce__()
方法。此外,__reduce__()
自动成为扩展版本的同义词。此方法的主要用途是为较旧的Python版本提供向后兼容的reduce值。
外部对象的持久性
为了对象持久性的好处,该pickle
模块支持对pickle数据流之外的对象的引用的概念。这些对象由持久性ID引用,该持久性ID应该是一串字母数字字符(对于协议0)[5]或者只是一个任意对象(对于任何较新的协议)。
这种持久性ID的解析不是由pickle
模块定义的; 它将委托此分辨率对皮克勒和Unpickler会,用户定义的方法persistent_id()
和 persistent_load()
分别。
要挑选具有外部持久性id的对象,pickler必须具有一个自定义persistent_id()
方法,该方法将对象作为参数并返回None
该对象的持久ID或持久ID。当None
返回时,只需皮克勒泡菜对象为正常。当返回持久性ID字符串时,pickler将对该对象以及标记进行pickle,以便unpickler将其识别为持久ID。
要取消排列外部对象,unpickler必须具有一个自定义 persistent_load()
方法,该方法接受持久ID对象并返回引用的对象。
这是一个全面的示例,展示了如何使用持久ID来通过引用来pickle外部对象。
# 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()
发货表
如果想要自定义某些类的酸洗而不打扰任何依赖于酸洗的其他代码,那么可以使用私有调度表创建一个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
pickle.Pickler
使用私有调度表创建一个实例,该表SomeClass
专门处理该类。或者,代码
class MyPickler(pickle.Pickler):
dispatch_table = copyreg.dispatch_table.copy()
dispatch_table[SomeClass] = reduce_SomeClass
f = io.BytesIO()
p = MyPickler(f)
执行相同操作,但MyPickler
默认情况下所有will的实例共享相同的调度表。使用该copyreg
模块的等效代码 是
copyreg.pickle(SomeClass, reduce_SomeClass)
f = io.BytesIO()
p = pickle.Pickler(f)
处理有状态对象
这是一个示例,显示如何修改类的酸洗行为。本TextReader
类打开一个文本文件,并返回每一次它的行号和行内容,readline()
方法被调用。如果 TextReader
实例被pickle,则保存除文件对象成员之外的所有属性。当实例未打开时,文件将重新打开,并从最后一个位置继续读取。该__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!'
限制全局
默认情况下,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
正如我们的例子所示,你必须小心你允许被打开的东西。因此,如果担心安全性,您可能需要考虑替代方案,例如编组API xmlrpc.client
或第三方解决方案。
示例
对于最简单的代码,请使用dump()
和load()
函数。
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)
以下示例读取生成的pickle数据。
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 函数不能被腌制的原因:所有 lambda 函数都使用相同的名称: <lambda> 。 |
[3] | 提出的例外可能是一个ImportError 或一个, AttributeError 但它可能是其他的东西。 |
[4] | 该copy 模块使用此协议进行浅层和深层复制操作。 |
[5] | 对字母数字字符的限制是由于协议0中的持久ID由换行符分隔。因此,如果持久性ID中出现任何类型的换行符,则生成的pickle将变得不可读。 |