python

关注公众号 jb51net

关闭
首页 > 脚本专栏 > python > Python Pickle序列化对象

Python使用Pickle模块序列化对象的完整指南

作者:小庄-Python办公

在 Python 的世界里,我们经常需要处理各种各样的对象,这篇文章主要介绍了Python使用Pickle模块序列化对象,感兴趣的小伙伴可以跟随小编一起学习一下

1. 什么是 Pickle?不仅仅是“打包”那么简单

在 Python 的世界里,我们经常需要处理各种各样的对象:从简单的列表、字典,到复杂的自定义类实例。当程序运行时,这些对象都活在内存里,一旦程序结束或计算机关机,它们就会烟消云散。如果我们想把这些对象“持久化”保存下来,或者在网络上传输,该怎么办?

这就是**序列化(Serialization)**发挥作用的时刻。简单来说,序列化就是把内存中的对象转换成可以存储(存入文件)或传输(通过网络发送)的字节流的过程。而反序列化,则是把字节流重新变回对象。

Python 标准库中有一个非常强大但也常被误解的模块——pickle

很多人把 Pickle 简单地理解为“打包”,这其实并不准确。虽然它确实能把多个对象“打包”成一个字节流,但它真正的核心能力在于几乎能序列化任何 Python 对象

Pickle 的超能力:它到底能做什么

与其他通用的序列化格式(如 JSON 或 XML)相比,Pickle 最显著的特点是通用性

简单上手:Pickle 的基本用法

Pickle 的 API 非常直观,核心函数主要有四个:dump(转储)、dumps(转储为字符串)、load(加载)、loads(从字符串加载)。

import pickle

# 定义一个稍微复杂的对象
class Player:
    def __init__(self, name, level):
        self.name = name
        self.level = level
    
    def upgrade(self):
        self.level += 1

# 创建实例
p1 = Player("Alice", 10)
data = {"players": [p1], "meta": "game_save_v1"}

# 1. 序列化到文件 (dump)
with open("save.pkl", "wb") as f:  # 注意:必须以二进制写模式 'wb' 打开
    pickle.dump(data, f)

# 2. 从文件反序列化 (load)
with open("save.pkl", "rb") as f:  # 注意:必须以二进制读模式 'rb' 打开
    loaded_data = pickle.load(f)

print(f"加载后的玩家: {loaded_data['players'][0].name}, 等级: {loaded_data['players'][0].level}")
# 输出: 加载后的玩家: Alice, 等级: 10

这段代码展示了 Pickle 的便捷性。p1 是一个自定义类的实例,通过 Pickle,它被完美地保存并复活了。

2. 深入 Pickle 的工作原理与协议版本

要真正掌握 Pickle,我们需要揭开它的面纱,看看它在“打包”的过程中到底发生了什么。

Pickle 虚拟机与协议

Pickle 并不是简单地把对象转成字节,它其实是在执行一个基于栈的序列化语言。当你调用 pickle.dump(obj, f) 时,Pickle 会把对象转换成一系列指令(opcodes),这些指令描述了如何重建该对象。当反序列化时,Pickle 虚拟机读取这些指令,压入栈,最终构建出对象。

这就引出了一个重要的概念:Pickle 协议(Protocol)

Python 提供了 5 个主要的协议版本(0-5),通过 pickle.dump(obj, file, protocol=...) 指定。

实用建议:除非你需要兼容非常古老的 Python 2 环境,否则请始终使用 protocol=pickle.HIGHEST_PROTOCOL(或者至少 Protocol 4),以获得最佳性能和功能支持。

甚至可以序列化代码

Pickle 甚至可以序列化函数和类定义,但这依赖于引用而非包含

def my_function():
    return "Hello"

# 序列化函数本身(实际上只保存了引用路径)
pickled_func = pickle.dumps(my_function)

# 反序列化
unpickled_func = pickle.loads(pickled_func)

# print(unpickled_func()) # 这在同一个脚本里通常能工作

这背后的逻辑是:Pickle 并没有把函数的字节码打包进去,而是记录了“这个函数叫什么,在哪个模块”。当反序列化时,它会去导入那个模块里的同名函数。这意味着,如果你把一个 Pickle 文件发给没有该函数定义的别人,反序列化就会失败。

3. 安全性:Pickle 是最危险的“打包”工具

这是 Pickle 最核心、最需要警惕的部分。永远不要 Unpickle(反序列化)来自不可信来源的数据!

为什么 Pickle 如此危险

JSON 或 XML 只是数据,它们本身不会执行任何操作。但 Pickle 不同,它是一组指令。当你反序列化时,你实际上是在执行一段代码。

Pickle 允许定义 __reduce__ 方法,该方法可以告诉 Pickle 在反序列化时应该执行什么操作。

攻击演示(概念性代码,请勿在生产环境运行):

假设黑客构造了一个恶意的 Pickle 文件,其中包含了一个恶意类:

import pickle
import os

class Malicious:
    def __reduce__(self):
        # 当这个对象被反序列化时,会自动执行 os.system
        return (os.system, ("echo '你的电脑被黑客控制了!'", ))

# 黑客生成恶意文件
with open("evil.pkl", "wb") as f:
    pickle.dump(Malicious(), f)

如果你不小心在你的服务器上加载了这个 evil.pkl 文件:

# 受害者代码
with open("evil.pkl", "rb") as f:
    data = pickle.load(f)  # 危险!此时代码已经执行!

结果就是 os.system("echo '...'") 被执行。这不仅仅是执行代码,黑客可以通过类似方式执行任何系统命令,比如删除文件、下载恶意软件、发起网络攻击等。

如何防范

4. Pickle 的局限性与调试技巧

虽然 Pickle 很强大,但它并非万能。在实际开发中,我们经常会遇到一些“坑”。

4.1 无法序列化的对象

有些对象是无法被 Pickle 的,通常会抛出 TypeError

解决方案:在自定义类中实现 __getstate____setstate__ 方法。

class ResourceHolder:
    def __init__(self):
        self.db_connection = "模拟连接对象"
        self.config = {"key": "value"}

    def __getstate__(self):
        # 序列化前调用:返回需要保存的字典
        state = self.__dict__.copy()
        # 移除不能序列化的部分
        del state['db_connection']
        return state

    def __setstate__(self, state):
        # 反序列化后调用:恢复对象状态
        self.__dict__.update(state)
        # 重新初始化连接
        self.db_connection = "重新建立的连接"

4.2 版本兼容性噩梦

这是 Pickle 最令人头疼的问题之一。

如果你的类定义发生了变化,比如:

那么反序列化旧的 Pickle 文件很可能会失败,或者产生意想不到的结果。

调试技巧

4.3 性能问题

虽然 Pickle 速度很快,但在处理海量小对象时,开销依然可观。对于科学计算(如 NumPy 数组),标准 Pickle 效率较低。

解决方案

5. 总结与最佳实践

Pickle 是 Python 生态中一把锋利的瑞士军刀。它让对象的持久化变得极其简单,能够处理复杂的 Python 数据结构。

核心观点总结:

到此这篇关于Python使用Pickle模块序列化对象的完整指南的文章就介绍到这了,更多相关Python Pickle序列化对象内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文