基于Pickle库的Python对象序列化探索

R语言有save()save.image()方法,可以保存整个工作区的镜像,用于下一次的使用。这个功能非常方便,可以节省调试过程中生成中间变量的时间。

那么python可以吗?

好像是可以的,pickle库是python的对象序列化模块。虽然它原则上只能序列化单个对象,但我们可以探索一下如何将全局变量进行序列化处理。

这里面需要用到的知识: dir()可以查看所有全局变量的名称。globals()可以以字典的方式访问所有全局变量。

探索结果如下:

1. dir()globals()可以查看python的所有全局变量

dir()globals()都属于python的内置函数,任何时候都能使用。其中,dir()用于返回当前本地作用域中的名称列表(即所有变量的名称)。而globals() 用于返回实现当前模块命名空间的字典,即变量的名称+变量的内容。

1
2
3
4
5
6
7
import struct,os,sys
a = 1
b = "Hello World"
dir() # show the names in the module namespace
# output: ['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'a', 'b', 'os', 'struct', 'sys']
globals()
# output: {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'struct': <module 'struct' from 'D:\\Anaconda3\\lib\\struct.py'>, 'os': <module 'os' from 'D:\\Anaconda3\\lib\\os.py'>, 'sys': <module 'sys' (built-in)>, 'a': 1, 'b': 'Hello World'}

可以看到,通过dir()函数可以查询到前面我们命名的变量ab的名称,而通过globals()我们可以查询到变量ab以及对应的内容。此外,还有一些名称中带有下划线的变量,如__name__等,这些属于python的内部变量。

2. pickle模块导出python对象到文件

模块 pickle 实现了对一个 Python 对象结构的二进制序列化和反序列化。 “pickling” 是将 Python 对象及其所拥有的层次结构转化为一个字节流的过程,而 “unpickling” 是相反的操作,会将(来自一个 binary file 或者 bytes-like object 的)字节流转化回一个对象层次结构。

——python官方文档

和R语言中的loadsave函数一样,pickle也是为了对象结构的存储。pickle提供了 pickle.dumppickle.dumpspickle.loadpickle.loads 这四个函数用于对象结构的导入导出。其中:

  • pickle.dump将对象 obj 封存以后的对象写入已打开的 file object _file_。
  • pickle.dumpsobj 封存以后的对象作为 bytes 类型直接返回,而不是将其写入到文件。
  • pickle.load从已打开的 file object 文件 中读取封存后的对象,重建其中特定对象的层次结构并返回。
  • pickle.loads重建并返回一个对象的封存表示形式 data 的对象层级结构。 data 必须为 bytes-like object
函数 导入导出 文件或对象
pickle.dump 导出python对象 到文件
pickle.dumps 导出python对象 到一个二进制对象
pickle.load 导入python对象 从文件
pickle.loads 导入python对象 从一个二进制对象

值得注意的是,python的pickle模块并没有R的load()save()那样智能,一些对象、模块或函数无法被序列化,因此需要一些额外的处理逻辑才能实现“保存整个工作区的镜像”的效果。

3. python保存整个工作区的镜像

如题。前几天的时候我在用jupyer lab做实验,由于想要保存一些中间结果方便未来的调试,于是探索了使用pickle导出工作区镜像的方法。

image.png

jupyter lab的环境中内置变量则更多(如上图),有一些类型的变量无法使用pickle进行导出。具体来说,一些module无法导出;对于一个同名的function,如果前后两次运行中修改了函数定义,这个函数也无法导出;此外,同一个类的对象,如果在实例化之后对类定义的代码也进行了修改,那么之前实例化的那些对象也无法导出。

如果强制导出这些变量,会出现类似下面这样的报错:

1
2
3
4
5
6
7
8
---------------------------------------------------------------------------
PicklingError Traceback (most recent call last)
Input In [662], in <cell line: 4>()
3 # 导出整个工作区镜像
4 with open('temp-2024-01-17-ensemble_model.image.pickle', 'wb') as f:
----> 5 pickle.dump(pipe2,f)

PicklingError: Can't pickle <class '__main__.ensemblePipe'>: it's not the same object as __main__.ensemblePipe

因此,我们定义global_img字典用于保存可导出的变量,预先剔除这些不可导出的变量。随后使用预导出的global_img代替globals()进行文件导出。

1
2
3
4
5
6
7
8
## 下面这段代码可以导出所有变量到`global_img`变量
global_img = {}

for i in dir():
obj = globals()[i]
if((type(obj).__name__ not in ['module','function','dict_keys']) and \
(i not in ['In','Out','base','exit','f','get_ipython','ensemblePipe','utils','pipe2','pipe_5','quit','exit']) and i[0]!="_"):
global_img[i] = obj

随后,使用下面的代码就可导出global_img到文件当中了。

1
2
3
4
import pickle
# 导出整个工作区镜像
with open('temp-2024-01-17-ensemble_model.image.pickle', 'wb') as f:
pickle.dump(global_img,f)

要从pickle文件中恢复这些变量,使用下面的代码即可(变量暂时存储在obj里面,后续可以把它们进行其他操作):

1
2
3
4
import pickle
# 将镜像文件导入到工作区
with open('temp-2024-01-17-ensemble_model.image.pickle', 'rb') as f:
obj = pickle.load(f)

除此之外,可以使用下面的代码段判断一个变量属于什么类型。这里使用了type()函数用来查看一个变量的类型。

1
2
3
for i in global_img.keys():
obj = globals()[i]
print(f"{i}:\t{type(obj)}")

以上。