在 Python 中,不可序列化对象 指的是无法直接使用 pickle
等序列化模块转换为可存储或传输格式的对象。常见的不可序列化对象包括:
- 文件句柄 (
open
返回的对象) - 线程、进程对象 (
threading.Thread
,multiprocessing.Process
) - 套接字 (
socket.socket
) - Lambda 函数(匿名函数)
- 包含无法序列化对象的自定义类
下面,我们介绍两种方法来处理包含这些不可序列化对象的序列化和反序列化方法。
1. 问题场景
当我们创建的实例对象中,包含上述一些不可实例化的对象,所以在进行序列化的时候会出现错误。
import pickle class Demo: def __init__(self, filename): print('对象实例化') self.filename = filename self.file = open(filename, 'w') def test(): # 实例化对象 demo = Demo('example.txt') # 序列化对象 pickle.dump(demo, open('demo.bin', 'wb')) # 反序列化对象 demo = pickle.load(open('demo.bin', 'rb')) if __name__ == '__main__': test()
程序报错如下:
对象实例化 Traceback (most recent call last): File "C:\Users\china\PycharmProjects\pickle反序列化漏洞\main5.py", line 22, in <module> test() File "C:\Users\china\PycharmProjects\pickle反序列化漏洞\main5.py", line 16, in test pickle.dump(demo, open('demo.bin', 'wb')) TypeError: cannot pickle '_io.TextIOWrapper' object
2. 重写 setstate 和 getstate
当使用 pickle 进行对象序列化和反序列化时,我们可以通过 __setstate__ 和 __getstate__ 来过滤掉一些不可序列化的对象,从而正确完成对象的序列化和反序列化操作。这两个操作,主要都是针对 __dict__ 属性就操作,该特殊属性中存储了实例对象所有的实例属性。
import pickle import os class Demo: def __init__(self, filename): print('对象实例化') self.filename = filename self.file = open(filename, 'w') def __getstate__(self): """用于确定需要序列化的内容""" print('序列化时调用') # __dict__ 用于存储所有的实例属性 state = self.__dict__.copy() # 从 __dict__ 中删除不可实例化的实例属性 state.pop('file', None) return state def __setstate__(self, state): """用于更新对象属性""" print('反序列化时调用', state) # 绑定新的实例属性 state['new_attr'] = 500 self.__dict__.update(state) # 由于反序列化时,不会重新调用初始化方法 # 所以,需要在此处手动打开文件 self.file = open(self.filename, 'a') def test(): # 创建对象 demo = Demo('example.txt') # 序列化对象 pickle.dump(demo, open('demo.bin', 'wb')) # 反序列化对象 demo = pickle.load(open('demo.bin', 'rb')) print(demo.new_attr) if __name__ == '__main__': test()
对象实例化 序列化时调用 反序列化时调用 {'filename': 'example.txt'} 500
3. 重写 reduce
如果我们不愿意写上面的两个方法来干涉序列化过程,也可以通过 __reduce__ 方法来实现。其基本思想和前面介绍的一样,也是通过修改 __dict__ 中的属性,来选择性进行序列化和反序列化操作。不同的是,该函数返回的类型要求是一个元组:
- 第一个值:可调用对象,一般都是 self.__class__,即:类对象
- 第二个值:这个值是一个元组类型,其元素为 self.__class__ 调用时传递的参数
- 第三个值:这个值是一个字典,包括需要序列化的所有实例属性
import pickle import os class Demo: def __init__(self, filename): print('对象实例化') self.filename = filename self.file = open(filename, 'w') def __reduce__(self): print('序列化和反序列化时调用') # 删除不能实例化的对象 self.__dict__.pop('file') # 添加新的实例属性 self.__dict__['new_attr'] = 500 # 反序列化时,会重新调用初始化方法 return (self.__class__, (self.filename,), self.__dict__) def test(): # 实例化对象 demo = Demo('example.txt') # 序列化对象 pickle.dump(demo, open('demo.bin', 'wb')) # 反序列化对象 demo = pickle.load(open('demo.bin', 'rb')) print(demo.new_attr) if __name__ == '__main__': test()
对象实例化 序列化和反序列化时调用 对象实例化 500