Python 对象序列化神器 ——pickle 深度剖析

Python 对象序列化神器 ——pickle 深度剖析

在 Python 编程世界里,数据的存储与传输是常见需求。pickle模块作为 Python 标准库的一员,承担着对象序列化和反序列化的重任。本文将深入探索pickle模块,带你从基础概念到实际应用,全面掌握这一强大工具,无论是数据持久化、网络传输,还是复杂对象处理,都能游刃有余。

文章目录

  • Python 对象序列化神器 ——pickle 深度剖析
    • 一、pickle 模块基础概念
      • (一)什么是序列化与反序列化
      • (二)pickle 模块的安全性问题
      • (三)与其他模块的比较
    • 二、pickle 模块的使用方法
      • (一)模块接口函数
      • (二)协议版本
      • (三)Pickler 和 Unpickler 类
      • (四)可被序列化 / 反序列化的对象
    • 三、pickle 模块的高级应用
      • (一)封存类实例
      • (二)持久化外部对象
      • (三)Dispatch 表
      • (四)处理有状态的对象
      • (五)类型、函数和其他对象的自定义归约
      • (六)外部缓冲区
    • 四、限制全局变量
    • 总结
    • TAG
    • 相关学习资源:

一、pickle 模块基础概念

(一)什么是序列化与反序列化

序列化,简单来说,就是把 Python 中的各种复杂对象(像列表、字典、自定义类的实例等)转化成一种可以存储或传输的格式,这个过程就好比把一堆物品打包整理,方便搬运和存放。而反序列化则是反向操作,将存储或传输的数据还原成原来的对象,如同打开包裹,取出里面的物品并恢复原样。pickle模块在 Python 中专门负责这两项操作,“pickling” 指的是序列化,“unpickling” 就是反序列化 。

(二)pickle 模块的安全性问题

使用pickle时,安全性是重中之重。恶意构造的 pickle 数据在解封(反序列化)时可能执行任意代码,这就像打开了一个装满危险物品的包裹,会对系统造成严重威胁。比如,下面这个恶意示例:

import pickle
pickle.loads(b"cos\nsystem\n(S'echo hello world'\ntR.")

这段代码会导入os.system()函数并执行字符串参数中的命令。所以,绝对不要对不信任来源的数据进行解封操作。为了保障安全,可以考虑使用hmac对数据签名,或者在处理不信任数据时选择更安全的json格式 。

(三)与其他模块的比较

  1. 与 marshal 模块的区别
    比较项 pickle 模块 marshal 模块
    对象引用处理 跟踪已序列化对象,共享对象和递归对象处理良好 不跟踪,递归对象会使解释器崩溃,不支持对象共享
    用户定义类及实例支持 能存储和还原类实例,但类定义需可导入 不支持序列化用户定义类及其实例
    跨版本兼容性 选择合适协议可实现跨版本兼容 格式不保证移植性,主要服务于.pyc 文件,可能破坏向后兼容
  2. 与 json 模块的区别
    比较项 pickle 模块 json 模块
    数据格式 二进制格式 文本格式(输出 unicode 文本,常以 utf - 8 编码)
    可读性 不可直观阅读 可直观阅读
    互操作性 Python 专用 广泛用于 Python 系统之外,可互操作
    数据类型支持 能表示大量 Python 数据类型,包括自定义类 只能表示 Python 内置类型的子集,默认不支持自定义类
    安全性风险 反序列化不信任数据可能执行任意代码 反序列化不信任数据本身不会造成此漏洞

二、pickle 模块的使用方法

(一)模块接口函数

  1. dump 和 dumps 函数
    • pickle.dump(obj, file, protocol=None, *, fix_imports=True, buffer_callback=None):将对象obj序列化后写入已打开的文件对象file。例如:
import pickle
data = {'name': 'Alice', 'age': 30}
with open('data.pkl', 'wb') as f:
    pickle.dump(data, f)
  • pickle.dumps(obj, protocol=None, *, fix_imports=True, buffer_callback=None):把对象obj序列化后直接返回bytes类型数据,而不是写入文件。如:
data = {'name': 'Bob', 'age': 25}
pickled_data = pickle.dumps(data)
print(pickled_data)
  1. load 和 loads 函数
    • pickle.load(file, *, fix_imports=True, encoding='ASCII', errors='strict', buffers=None):从已打开的文件对象file中读取序列化数据,重建对象层次结构并返回。例如:
with open('data.pkl', 'rb') as f:
    loaded_data = pickle.load(f)
print(loaded_data)
  • pickle.loads(data, /, *, fix_imports=True, encoding='ASCII', errors='strict', buffers=None):根据传入的bytes类型数据data,重建对象层级结构并返回。比如:
pickled_data = pickle.dumps({'name': 'Charlie', 'age': 35})
new_data = pickle.loads(pickled_data)
print(new_data)

(二)协议版本

pickle模块目前有 6 种协议版本:

协议版本 特点 兼容性
v0 原始 “人类可读” 协议 向后兼容早期 Python 版本
v1 较早的二进制格式 与早期 Python 版本兼容
v2 Python 2.3 引入,为新式类提供更高效封存机制
v3 Python 3.0 引入,显式支持 bytes 字节对象 不能用 Python 2.x 解封,是 Python 3.0 - 3.7 的默认协议
v4 Python 3.4 添加,支持存储大对象,优化数据格式 是 Python 3.8 的默认协议
v5 Python 3.8 加入,支持带外数据,加速带内数据处理

(三)Pickler 和 Unpickler 类

  1. Pickler 类
    • pickle.Pickler(file, protocol=None, *, fix_imports=True, buffer_callback=None):用于创建一个序列化对象,接受一个二进制文件用于写入 pickle 数据流。比如:
import pickle
import io
data = [1, 2, 3]
f = io.BytesIO()
p = pickle.Pickler(f, protocol=4)
p.dump(data)
  • dump(obj):将对象obj序列化后写入已打开的文件对象。
  • persistent_id(obj):可被子类重写,用于处理外部对象的持久化 ID。
  • dispatch_table:用于注册 reduction 函数,可自定义序列化过程。
  • reducer_override(obj):在子类中定义,优先级高于dispatch_table中的 reducer。
  1. Unpickler 类
    • pickle.Unpickler(file, *, fix_imports=True, encoding='ASCII', errors='strict', buffers=None):用于创建一个反序列化对象,接受一个二进制文件用于读取 pickle 数据流。例如:
import pickle
import io
f = io.BytesIO(b'\x80\x04\x95\x0f\x00\x00\x00\x00\x00\x00\x00]\x94(K\x01K\x02K\x03e.')
u = pickle.Unpickler(f)
loaded_data = u.load()
print(loaded_data)
  • load():从文件对象中读取序列化数据,重建对象层次结构并返回。
  • persistent_load(pid):用于处理持久化 ID 对应的对象。
  • find_class(module, name):可被子类重写,控制加载对象的类型和方式,增强安全性。

(四)可被序列化 / 反序列化的对象

能被pickle处理的对象包括:内置常量(如NoneTrueFalse等)、数值类型(整数、浮点数、复数)、字符串、字节串、字节数组、包含可序列化对象的容器(元组、列表、集合、字典 )、模块最高层级的函数(用def定义)、模块最高层级的类,以及满足特定条件的类实例 。

三、pickle 模块的高级应用

(一)封存类实例

类可以通过定义特殊方法来控制实例的序列化和反序列化行为:

  1. __getnewargs_ex__():用于控制解封时传给__new__()方法的参数,返回(args, kwargs)对。
  2. __getnewargs__():类似__getnewargs_ex__(),但只支持位置参数,返回一个tuple类型的args
  3. __getstate__():重写该方法可自定义实例被序列化的内容。
  4. __setstate__ (state):在解封时被调用,用于恢复实例状态。
  5. __reduce__():功能强大但易出错,返回字符串或元组,用于定义对象的序列化方式。
  6. __reduce_ex__(protocol):与__reduce__()类似,但接受协议版本参数,可覆盖__reduce__()的行为 。

(二)持久化外部对象

通过持久化 ID,pickle可以引用已封存数据流之外的对象。发送端的Pickler需实现persistent_id()方法返回对象的持久化 ID,接收端的Unpickler要实现persistent_load()方法根据 ID 返回对象 。

(三)Dispatch 表

当需要对某些类进行自定义序列化,又不想在类中添加代码时,可使用dispatch表。copyreg模块管理全局dispatch表,可创建自定义dispatch表来定制特定类的序列化过程 。

(四)处理有状态的对象

通过__getstate__()__setstate__()方法,可修改类的封存行为,实现有状态对象在序列化和反序列化过程中的状态保存与恢复 。

(五)类型、函数和其他对象的自定义归约

dispatch_table不够灵活时,可子类化Pickler类并实现reducer_override()方法,基于对象类型以外的规则定制封存 。

(六)外部缓冲区

在处理海量数据传输时,为减少内存复制,可利用pickle第 5 版及以上协议的外部传输功能。数据提供方需实现特定的__reduce_ex__()方法返回PickleBuffer实例,使用方通过传递buffer_callbackbuffers参数来优化数据传输 。

四、限制全局变量

默认情况下,解封会导入 pickle 数据中的类或函数,存在安全风险。可通过定制Unpickler.find_class()方法,限制解封的全局对象,只允许加载安全的类或函数 。

总结

pickle模块在 Python 对象序列化和反序列化方面功能强大,但使用时要格外注意安全性。通过本文对其基础概念、使用方法、高级应用以及安全限制的详细讲解,希望你能熟练掌握这一工具,在数据处理、存储和传输场景中灵活运用。在实际项目中,根据具体需求选择合适的协议版本、正确处理类实例和外部对象,合理利用各种定制功能,确保程序高效、安全地运行。

TAG

Python、pickle 模块、对象序列化、反序列化、数据存储、数据传输、安全编程

相关学习资源:

  • Tekin的Python编程秘籍库: Python 实用知识与技巧分享,涵盖基础、爬虫、数据分析等干货 本 Python 专栏聚焦实用知识,深入剖析基础语法、数据结构。分享爬虫、数据分析等热门领域实战技巧,辅以代码示例。无论新手入门还是进阶提升,都能在此收获满满干货,快速掌握 Python 编程精髓。
  • Python 官方文档:https://docs.python.org/zh-cn/3.12/library/pickle.html ,本文的主要参考资料,提供了pickle模块全面且权威的介绍,从基础概念到高级应用,包含大量代码示例。
  1. 《Python 核心编程》:书籍详细介绍了 Python 的各类核心知识,其中对pickle模块的讲解深入,结合实际案例,帮助读者更好地理解和运用该模块,提升 Python 编程能力。

你可能感兴趣的:(Python,编程秘籍库,Python网络编程,python,对象序列化,pickle)