pickle/json 序列化
文件系统,机器学习,大数据等,都少不了数据文件。Python 也提供了一些比较方便序列化的存储的组件。 什么是序列化(Serialization)呢,说简单也简单,就是把像字典,列表这类的数据,打包保存在电脑硬盘中。 如果粗略的理解对比,就有点像 zip 和 unzip 的过程。
在这一节主要涉及到的功能:
Pickle¶
我在什么时候会想到要用 pickle 呢?就是我想要把一整个 class 都打包的时候,我会想起要用 pickle。 但是用 pickle 打包 class 是一件有风险的事情,有时候还会失败。我们在后面一点来看看哪些时候会失败,我们又怎么避免打包失败。
下面,我们先来看看一个最简单的例子,怎么把一个字典打包到 pickle 里,打包以后又是什么样子?
xxxxxxxxxx
import pickle
data = {"filename": "f1.txt", "create_time": "today", "size": 111}
pickle.dumps(data)
如果你点击了运行,你可以看到这个字典被 pickle 以后,已经面目全非,你不能直接读出来里面的信息,因为这些信息已经被编码了。 所以当你在用 pickle 的时候另外一点重要的是,你需不需要能看懂被打包的数据,如果没有这个需求,那你就可以用 pickle, 不然我们后面还会介绍一个叫 json 的打包库,它打出来的包,就是你能看懂的东西了。
好,前面我们只是让 data
变成一串编码字节,下面我们可以用 pickle.dump()
将字典直接转换成一个文件。
xxxxxxxxxx
data = {"filename": "f1.txt", "create_time": "today", "size": 111}
with open("data.pkl", "wb") as f:
pickle.dump(data, f)
import os
os.listdir()
光存下来肯定没意思,我们还的在另外一个时刻,将文件读出来。pickle.load()
就能帮上忙。 我们用 open
的方式把文件都读出来,然后再用 pickle
对其解析,还原成最开始的那一个字典。
xxxxxxxxxx
with open("data.pkl", "rb") as f:
data = pickle.load(f)
print(data)
除了常见的字典,列表,元组这类的数据,pickle 甚至都可以打包 Python 的功能以及类。打包功能的意义我觉得并不是很大, 但是能打包类还是比较有意思的。比如我有一个文件系统,我定义了一个 File
类,而且基于它生成了很多 file
实例。 这些实例都是可以被 pickle 的。
注意,在反序列化 unpickle 的时候,这个 File
的 class 一定要有,不然反序列化会因为找不到 File
类而失败的。 这比较好理解,就是按模具重组原件,你没有这个 File
class 模具,pickle 的原件也没有一个模具能够组装起来。
xxxxxxxxxx
class File:
def __init__(self, name, create_time, size):
self.name = name
self.create_time = create_time
self.size = size
def change_name(self, new_name):
self.name = new_name
data = File("f2.txt", "now", 222)
# 存
with open("data.pkl", "wb") as f:
pickle.dump(data, f)
# 读
with open("data.pkl", "rb") as f:
read_data = pickle.load(f)
print(read_data.name)
print(read_data.size)
#################
# 注意,可能是浏览器的原因,点击运行后会报错,但是在正常的 Python 执行环境中,
# 运行是没有问题的,运行结果如下:
#################
"""
f2.txt
222
"""
你看最后,unpickle 出来的东西还是一个 class 的实例。你可以按正常的 class 方式使用这个 unpickle 的东西。
有些类型的对象是不能被序列化的。这些通常是那些依赖外部系统状态的对象, 比如打开的文件,网络连接,线程,进程,栈帧等等。 如果你在 class 中把上述东西赋值到了 class 的属性上,比如下面的 self.file = open()
,这样的 class 在 pickle 的时候会报错的。
xxxxxxxxxx
class File:
def __init__(self, name, create_time, size):
self.name = name
self.create_time = create_time
self.size = size
self.file = open(name, "w")
data = File("f3.txt", "now", 222)
# pickle 存,会报错
with open("data.pkl", "wb") as f:
pickle.dump(data, f)
#################
# 注意,可能是浏览器的原因,点击运行后会报错,
# 我们知道它会报错,但是报的错不是它说的那样,而是下面这样:
#################
"""
Traceback (most recent call last):
File "<input>", line 11, in <module>
TypeError: cannot pickle '_io.TextIOWrapper' object
"""
如果你硬要用 pickle 保存,我们也还是有解决方案的。 用户自定义类可以通过提供 __getstate__()
和 __setstate__()
方法来绕过 pickle 的这些限制。 pickle.dump()
会调用 __getstate__()
获取序列化的对象。 __setstate__()
在反序列化时被调用。
xxxxxxxxxx
class File:
def __init__(self, name, create_time, size):
self.name = name
self.create_time = create_time
self.size = size
self.file = open(name, "w")
def __getstate__(self):
# pickle 出去需要且能被 pickle 的信息
pickled = {"name": self.name, "create_time": self.create_time, "size": self.size}
return pickled
def __setstate__(self, pickled_dict):
# unpickle 加载回来,重组 class
self.__init__(
pickled_dict["name"], pickled_dict["create_time"], pickled_dict["size"])
data = File("f3.txt", "now", 222)
# 存
with open("data.pkl", "wb") as f:
pickle.dump(data, f)
# 读
with open("data.pkl", "rb") as f:
read_data = pickle.load(f)
print(read_data.name)
print(read_data.size)
#################
# 注意,可能是浏览器的原因,点击运行后会报错,
# 在正常的 Python 环境中,结果如下:
#################
"""
f3.txt
222
"""
pickle 我们就说这么多,相信你已经大概知道了如何使用它。我们下面来看看另一种好用的序列化工具,并介绍一下它的特点。
Json¶
json 和 pickle 很像。如果有一定编程基础的同学,会一眼看出,json 不是一种数据格式吗?怎么就变成一个 Python 库了? 没错,Python 中的 json 库,就是来处理 json 形式的数据的。
什么是 json 数据?一般来说,Python 里的字典,列表都可以是 json 数据格式。如果我们用 Python 里的 json 库来读一个 Python 字典, 会处理成什么样呢?
xxxxxxxxxx
import json
data = {"filename": "f1.txt", "create_time": "today", "size": 111}
j = json.dumps(data)
print(j)
print(type(j))
其实也没变成什么稀奇古怪的样子,就是照搬字典的样子,变成了一个字符串形式的字典。没错,这就是 json 数据啦。 在网页请求中,我们经常会使用到这样的 json 数据。
如果我们在 Python 中,需要这样明文来存储数据的话,使用 json.dump()
,和 pickle.dump()
非常像的方式就好了。
xxxxxxxxxx
data = {"filename": "f1.txt", "create_time": "today", "size": 111}
with open("data.json", "w") as f:
json.dump(data, f)
print("直接当纯文本读:")
with open("data.json", "r") as f:
print(f.read())
print("用 json 加载了读:")
with open("data.json", "r") as f:
new_data = json.load(f)
print("字典读取:", new_data["filename"])
但是 json 相比 pickle 还是有它不及的点。我们上面已经看到,pickle 可以很轻松的打包 Python 的 class。 但是 json 不能序列化保存 class。你只能挑出来重要的信息,放到字典或列表里,然后再用 json 打包字典。 下面就是一段 json 打包 class 会报错的代码。
xxxxxxxxxx
class File:
def __init__(self, name, create_time, size):
self.name = name
self.create_time = create_time
self.size = size
def change_name(self, new_name):
self.name = new_name
data = File("f4.txt", "now", 222)
# 存,会报错
with open("data.json", "w") as f:
json.dump(data, f)
Pickle 和 Json 的不同¶
上面说了挺多的,你可能也比较难一次性消化。这不,我给你做一张表,你就能轻松对比这两种方法的异同之处啦。
对比 | Pickle | Json |
---|---|---|
存储格式 | Python 特定的 Bytes 格式 | 通用 JSON text 格式,可用于常用的网络通讯中 |
数据种类 | 类,功能,字典,列表,元组等 | 基本和 Pickle 一样,但不能存类,功能 |
保存后可读性 | 不能直接阅读 | 能直接阅读 |
跨语言性 | 只能用在 Python | 可以跨多语言读写 |
处理时间 | 长(需编码数据) | 短(不需编码) |
安全性 | 不安全(除非你信任数据源) | 相对安全 |
pickle和json各有优点
pickle:可以打包类、功能
json:无需编码,节省时间;可以跨多种语言;相对安全;可以直接阅读;可以用于网络通讯