【Spinning Up】一文弄懂序列化模块json、pickle和cloudpickle

【Spinning Up】一文弄懂序列化模块json、pickle和cloudpickle

前言:

最近在花大量的时间去解析spinning up的多进程并行模块,由于我没有相关的基础,官方的文档在这块也是略过。
甚至连第三方博客都非常少,让我极为头疼。
因此只能花苦功夫,把它的并行模块,每一句都弄明白…

前段时间看了mpi4py, subprocess, 修饰器,lambda,等花里胡哨的操作。

为什么要用到这个操作呢?我贴一段下一篇博客的内容~

先通过cloudpickle.dumps对函数thunk进行序列化,编码成一个二进制文件;
这个二进制文件可以通过pickle.loads解码成原本的函数,参数什么的都不变。
该是什么功能就是什么功能;
但为什么花这么大代价,整这一通操作呢?
因为如果在当前脚本下,连续跑一个DDPG-tf1,TD3-tf1,就会报错,因为当前进程里
之前DDPG的变量还没有清空,因此有如下报错:
ValueError: Variable main/pi/dense/kernel already exists, disallowed.
这个就很尴尬了,我是没有好的方案去清除,当前进程之前的那些变量;
因此只好将spinup自带的这个打包函数+单一超参数->启动子进程->解码->在子进程执行单一超参数的函数

今天轮到pickle了。

简介:

pickle:英文翻译是泡菜,腌制的意思。

它的功能确实很像,我在知乎上看到,有人描述:

何谓序列化(serialization)
每种编程语言都有各自的数据类型, 将属于自己语言的数据类型或对象转换为可通过网络传输或可以存储到本地磁盘的数据格式(如:XML、JSON或特定格式的字节串)的过程称为序列化(seralization);反之则称为反序列化。

但是json只能处理基础的数据类型,对于函数对象,类对象,就无法胜任了。
因此需要有一个更加强悍的模块:

这个时候Pickle模块就派上用场了,它可以将对象转换为一种可以传输或存储的格式。
而cloudpickle makes it possible to serialize Python constructs not supported by the default pickle module from the Python standard library.
即拓展了pickle的功能,让一些其他的数据类型也能被序列化。

对于json和pickle这两个模块,基本上就只需要记住四个函数的作用就行了;

我看很多文章都是代码注释,但其实还不够直观,最好还是上例程,看例程的输出,稍微加点注释就能深刻理解,和使用~

示例代码:

一、json和pickle的编码(.dumps()函数)差别:

import pickle
import json

outs = {
    
    "time": 12,
        "q_time": 1,
        "pi_time": 2}

# json和pickle的区别:
# 序列化一个对象为JSON格式,输出为字符串.
# dumps的直译是倾倒,用编码来理解,有点牵强~
outs_json_dumps = json.dumps(outs)
print("outs_json_dumps:", outs_json_dumps)

outs_pickle_dumps = pickle.dumps(outs)
print("outs_pickle_dumps:", outs_pickle_dumps)

打印结果:
outs_json_dumps: {“pi_time”: 2, “q_time”: 1, “time”: 12}
outs_pickle_dumps: b’\x80\x03}q\x00(X\x07\x00\x00\x00pi_timeq\x01K\x02X\x06\x00\x00\x00q_timeq\x02K\x01X\x04\x00\x00\x00timeq\x03K\x0cu.’

可以看出来的,都能对字典这个数据类型进行编码,json编码后为字符串类型,是可以直观的判断数据内容的,因此比较安全
但是pickle编码后的玩意儿,是二进制格式,根本看不出来里面到底是什么鬼东西。
因此不够安全,官方文档都建议了,不要轻易打开一个未知的编码文件~

二、json无法编码特殊对象,比如函数,类:

序列化函数~

import pickle
import json

def foo(**kwargs):
    for key, value in kwargs.items():
        print("%s=%s" % (key, value))

# json和pickle的区别:
foo_pickle_dumps = pickle.dumps(foo)
print("foo_pickle_dumps:", foo_pickle_dumps)

foo_json_dumps = json.dumps(foo)
print("foo_json_dumps:", foo_json_dumps)

打印结果:
foo_pickle_dumps: b’\x80\x03c__main__\nfoo\nq\x00.’
Traceback (most recent call last):
File “f:/rl_code/calibration/pickle_demo.py”, line 17, in
foo_json_dumps = json.dumps(foo)
File “D:\Users\lele\Anaconda3\envs\keras\lib\json_init_.py”, line 230, in dumps
return _default_encoder.encode(obj)
File “D:\Users\lele\Anaconda3\envs\keras\lib\json\encoder.py”, line 198, in encode
chunks = self.iterencode(o, _one_shot=True)
File “D:\Users\lele\Anaconda3\envs\keras\lib\json\encoder.py”, line 256, in iterencode
return _iterencode(o, 0)
File “D:\Users\lele\Anaconda3\envs\keras\lib\json\encoder.py”, line 179, in default
raise TypeError(repr(o) + " is not JSON serializable")
TypeError: <function foo at 0x0000016F7C2979D8> is not JSON serializable

可以看出来pickle毫无压力,照样能整,但是json就不支持了。
我们只需要记住,json只能对常见的数据类型对象进行编码就够了,比如字典,列表,元组;

三、pickle的加载对象:

用到函数pickle.loads()

import pickle
import json

def foo():
    print("hello world")
    
foo_pickle_dumps = pickle.dumps(foo)
print("foo_pickle_dumps:", foo_pickle_dumps)

foo_pickle_dumps_loads = pickle.loads(foo_pickle_dumps)
print("foo_pickle_dumps_loads:", foo_pickle_dumps_loads)

foo_pickle_dumps_loads()

打印结果:
foo_pickle_dumps: b’\x80\x03c__main__\nfoo\nq\x00.’
foo_pickle_dumps_loads: <function foo at 0x00000214A4ABB488>
hello world

可以看出来,通过dumps()编码后的函数变成了二进制后,再经过pickle.loads()解码后,有变成了活生生的正经函数了。

四、pickle.dumps() 和 pickle.dump()的区别,以及pickle.loads()和pickle.load()的区别;

这个没什么好说的,加了s的,传入的参数不需要路径,即不需要保存到本地,或者从本地读取。
没加s的,要加路径~
例程:

import pickle
import json

def foo():
    print("hello world")

# json和pickle的区别:
with open("foo_pickle_dump.pkl", 'wb') as f:
    pickle.dump(obj=foo, file=f)
print("save sucess!")
foo_pickle_dump_load = pickle.load(open('foo_pickle.pkl', 'rb'))
print("foo_pickle_dump_load:", foo_pickle_dump_load)
foo_pickle_dump_load()

打印结果:
save sucess!
foo_pickle_dump_load: <function foo at 0x0000017F30057AE8>
hello world

保存到本地的是一个foo_pickle_dump.pkl文件,打开后乱码;
加载后,可以看出函数名foo,这个就很棒了~

总结:

到这里基本上可以算是,一文弄懂pickle了;
这算是spinning up代码解析的一个重要部分了。

联系方式

ps: 欢迎做强化的同学加群一起学习:

深度强化学习-DRL:799378128

欢迎关注知乎帐号:未入门的炼丹学徒

CSDN帐号:https://blog.csdn.net/hehedadaq

猜你喜欢

转载自blog.csdn.net/hehedadaq/article/details/114339620
今日推荐