当程序运行起来时(也就是进程),所有的变量都保存在内存中, 当进程结束后,该进程申请的内存都要被操作系统回收,我们在内存中的数据就会丢失, 如果想使数据持久化,我们可以把数据存储在磁盘上。但是文件对象只能处理字符流或字节流,如果我们要存储的数据类型为其它类型(int,float,bool,list,tuple,dict等等)应该怎么办呢?
不但读写磁盘需要通过文件对象,网络传输数据也需要通过文件对象,因为网络 IO 是指通过文件对象读写网卡而已,磁盘 IO 是指通过文件对象读写磁盘而已。我们知道文件对象只能处理字符流或字节流,如果我们想把非字符或字节类型(int,float,bool,list,tuple,dict等等)的数据通过网络发送到其它机器上应该怎么办呢?
根据以上两个问题,我们本节课就来学习一下序列化:我们把变量从内存中变成可存储或传输的过程(也就是变成字符流或字节流的过程),称之为序列化,序列化之后,就可以把序列化后的内容写入磁盘,或者通过网络传输到别的机器上。反之,能把序列化后的字符流或字节流还原成序列化之前的类型数据的过程叫反序列化。
在 Python 中有两个模块可以对数据进行序列化和反序列化:cPickle 和 pickle。这两个模块功能是一样的,区别在于 cPickle 是 C 语言写的,速度快;pickle 是纯 Python 写的,速度慢。我们用的时候可以先尝试导入 cPickle,如果失败,再导入 pickle。注意:在 Python 3 中,pickle 序列化的结果是字节流。
try: import cPickle as pickle except ImportError: import pickle mylist = [1, True, "hello"] seqdata = pickle.dumps(mylist) # 序列化成字节流 print(type(seqdata)) # list 类型数据序列化成 Bytes 类型 print(seqdata) # 序列化后的字节流 mylistreseq = pickle.loads(seqdata) # 把被序列化的字节流反序列化成 list print(type(mylistreseq)) # list 类型 print(mylistreseq) # 反序列化后的 list
我们可以把 Python 内置的各种类型的数据序列化成字符流或字节流数据,然后用文件对象存储到磁盘上。 同样,我们也可以用文件对象读出这些序列化后的字符流或字节流,然后反序列化成它原来的类型数据到内存中。
try: import cPickle as pickle except ImportError: import pickle mylist = [1, True, ("hello", None)] f = open("d:/test.txt", "wb") # 用 wb 写入的是字节流 seqdata = pickle.dumps(mylist) # 序列化成字节流 f.write(seqdata) f.close() f = open("d:/test.txt", "rb") # 用 rb 读取的是字节流 seqdata = f.read() f.close() mylistreseq = pickle.loads(seqdata) # 反序列化后的 list print(mylistreseq)
对于我们自定义类型,同样可以使用 pickle 进行序列化。
try: import cPickle as pickle except ImportError: import pickle class myclass(object): def __init__(self): self.data = 250 def func(self): print("ok") myobject = myclass() xx = pickle.dumps(myobject) yy = pickle.loads(xx) print(yy.data) yy.func()
虽然我们可以使用 pickle 序列化自定义对象,但是只有序列化这个对象的程序本身认识该对象,如果我们把序列化后的字符串存到磁盘上,用另一个程序进行反序列化;或者通过网络发送给其它程序进行反序列化。我们都需要在反序列化的程序中定义该类,否则程序不知道这个类型是什么,就会报错。
try: import cPickle as pickle except ImportError: import pickle class myclass(object): def __init__(self): self.data = 250 def func(self): print("ok") myobject = myclass() xx = pickle.dumps(myobject) f = open("d:/tt.txt", "wb") f.write(xx) f.close()
''' 这是另一个程序 ''' import pickle try: import cPickle as pickle except ImportError: import pickle # 一定要定义一个和序列化程序中的那个类一样的类,否则会报错 class myclass(object): def __init__(self): self.data = 250 def func(self): print("ok") f = open("d:/tt.txt", "rb") xx = f.read() myobject = pickle.loads(xx) print(myobject) print(myobject.data) myobject.func()
序列化就像中介一样,它把我们内存中的某个类型的数据根据一定的算法转换成字符流或字节流。同样,反序列化就是把序列化后的字符流或字节流按一定的算法再还原成它原来的样子。注意 pickle 只支持序列化和反序列化 Python 中的各种类型的数据。我们考虑以下几种场景:1:如果我们文件内的数据是其它语言序列化后的,我们就没有办法用 pickle 模块进行反序列化。 2:更常见的需求是我们在做网络通信的程序,比如 C/S 模式的即时通讯程序,我们需要在客户端和服务器之间我们要做数据通信, 客户端和服务器采用的语言不可能是同一种,比如我们客户端用 Python 语言,服务器用 C++ 语言等。 3:还有现在的 B/S 模式的网站程序,前端用 js 语言,后端用 java,php,python等等。 这时候我们就需要一种各种主流语言都支持的模块,这个模块可以把不同语言的数据类型序列化成统一的数据类型,然后反序列成相应语言的数据类型, 这个模块就是 json 模块,json 就如英文一样,如果中文和日文需要交流,可以先翻译成英文,然后在翻译成自己的语言。
Python 内置了json 模块,可以实现序列化和反序列化,几乎所有的语言都支持 json,比如 C,C++,C#,Java,PHP,Python,Js等等,json 的使用方法和 pickle 类似。注意:json 序列化的结果是字符流。
import json mylist = [1, True, ("hello", None)] f = open("d:/test.txt", "w") seqdata = json.dumps(mylist) # 序列化成字符流 f.write(seqdata) f.close() f = open("d:/test.txt", "r") seqdata = f.read() f.close() mylistreseq = json.loads(seqdata) # 反序列化后的 list print(mylistreseq)
json 肯定只能支持所有语言都共有的数据类型,比如 int,float,bool 等这些类型,但是并不是所有语言的类型都是一样的,比如 Python 的 set 类型在其它语言中就没有,json 就不会支持对这种类型数据的序列化和反序列化。
import json myset = set([3, 4, 5]) data = json.dumps(myset) # 错误
Python 中的 tuple 类型在其它语言中也没有,但是 tuple 类型和 list 类型很相似,所以 json 会把 tuple 类型数据序列化和反序列化成 Python 的 list 类型数据。 如果是用其它语言进行反序列化我们序列化好的 Python 中的 tuple 类型数据,则会反序列化成其它语言对应的数组类型。
import json mytuple = (1, 2, 4) data = json.dumps(mytuple) dd = json.loads(data) print(type(dd)) # <type 'list'> print(dd) # [1, 2, 4]
Python 中的 dict 类型在其它语言中有类似的类型,比如 C++ 中的 map,Js 中的 object 等等,但是他们的格式有些不同,比如 C++ 中的 map 的键只允许字符串类型等等。我们用 json 序列化和反序列化 Python 的 dict 类型数据,就会只支持键为字符串的 dict,如果键是其它基本数据类型 json 模块会把他转为相应的字符串,如果是 tuple 类型则会报错。
import json seqdata = json.dumps({1: 1, 1.1: 2, "a": 3, None: 4, True: 5}) # 序列化成字符流,并且对不是字符串类型的键做强制转换 dd = json.loads(seqdata) print(dd) # {u'a': 3, u'1': 5, u'null': 4, u'1.1': 2} seqdata = json.dumps({(1,2): 1}) # 虽然字典本身没问题,但是使用 json 序列化会报错
我们可以把 json 表示的数据类型看成中介数据类型,任何语言的数据类型都对应着同样的 json 数据类型。 假如我们客户端用 Python 语言编写,服务器端用 C++ 语言编写,客户端需要发送数据给服务器端,就如我们把 Python 的数据类型看成日文,json 的数据类型看成英文,C++ 的数据类型看成中文, 先把日文翻译成英文,然后再翻译成中文,这样 C++ 就看的明白 Python 的数据类型了。 下面我们看下 Python 的数据类型对应的 json 数据类型的关系图,当然其它语言的数据类型也对应这同样的 json 数据类型。
python 类型 | json 类型 | 说明 |
---|---|---|
dict | {} | 序列化时:dict 的键只能为字符串,否则会做相应转换或报异常 |
list 或 tuple | [] | 序列化时:tuple 会被序列化成 list 形式的字符流 |
str 或 unicode | unicode | 序列化和反序列化时:默认会把字符串序列化和反序列化成 unicode 编码格式 |
int | int | 无差别 |
float | float | 无差别 |
bool | bool | 序列化时:会把 True 会变成 "true" 字符流,False 变成 "false" 字符流;反序列化时:会把 "true" 字符流变成 True,"false" 字符流变成 False |
None | null | 序列化时:会把 None 会变成 "null" 字符流;反序列化时:会把 "null" 字符流变成 None |
set | 错误 | json 不支持对 set 类型进行序列化 |
我们研究一下 json 序列化是怎么做的,我们发现 json 序列化后的字符流可读性很好,虽然是字符串,但看上去保留着原来的形式。
import json a = 1 b = 1.1 c = True d = None e = "hello" f = [1, 2] g = (1, 2) h = {"a":1, "b": 2} print(json.dumps(a)) # '1' print(json.dumps(b)) # '1.1' print(json.dumps(c)) # 'true' print(json.dumps(d)) # 'null' print(json.dumps(e)) # '"hello"' print(json.dumps(f)) # '[1, 2]' print(json.dumps(g)) # '[1, 2]' print(json.dumps(h)) # '{"a":1, "b": 2}'
我们知道 Python 自有的数据类型有哪些可以使用 json 进行序列化,但是我们自定义的类对象是否可以用 json 序列化呢,比如我们定义一个 Human 类,然后对 Human 类的对象进行序列化。
import json class Human(object): def __init__(self, name, age, sex): self.name = name self.age = age self.sex = sex oneman = Human('如花', 18, "女") seqdata = json.dumps(oneman) # 抛出异常,...is not JSON serializable
上面的代码之所以无法把 Human 类对象序列化为 json,是因为默认情况下,json 模块中的 dumps 函数不知道如何将 Human 对象变为一个 json 对象。 dumps 函数的可选参数 default 就是把任意一个对象变成一个可序列为 json 的对象,我们只需要为 Human 类专门写一个转换函数,再把函数传进去即可。
import json class Human(object): def __init__(self, name, age, sex): self.name = name self.age = age self.sex = sex def human2dict(oneman): return { 'name': oneman.name, 'age': oneman.age, 'sex': oneman.sex } oneman = Human('如花', 18, "女") print(json.dumps(oneman, default=human2dict))
因为通常类的对象都有一个 __dict__ 属性,它就是一个 dict,用来存储实例变量。也有少数例外,比如定义了 __slots__ 的 类。同样的道理,如果我们要把 json 反序列化为一个 human 类的对象,json 模块的 loads 函数首先转换出一个 dict 对象,然后,我们传入的 object_hook 函数负责把 dict 转换为 human 类的对象。
import json class Human(object): def __init__(self, name, age, sex): self.name = name self.age = age self.sex = sex def human2dict(oneman): return { 'name': oneman.name, 'age': oneman.age, 'sex': oneman.sex } def dict2human(d): return Human(d['name'], d['age'], d['sex']) oneman = Human('如花', 18, "女") json_str = json.dumps(oneman, default=human2dict) # 序列化 oneman 对象 print(json_str) # '{"name": "如花", "age": 18, "sex": "女"}' recoveroneman = json.loads(json_str, object_hook=dict2human) # 反序列成 Human 对象 print(recoveroneman)
我们仔细思考一下,自定义对象的属性是什么,其实自定义对象的属性最终还是由 Python 内置的数据类型表示的。我们对自定义对象进行序列化,本质是就是对对象的所有属性进行序列化,这些属性最终是由 Python 内置的数据类型表示的,我们只需要序列化这些数据就可以了,无论我们是把这些序列化后的数据存储在磁盘上还是通过网络发送给其它机器,只需要存储或发送对象的所有属性即可。
import json class Human(object): def __init__(self, name, age, sex): self.name = name self.age = age self.sex = sex oneman = Human('如花', 18, "女") onemandata = {"name": oneman.name, "age": oneman.age, "sex": oneman.sex} seqdata = json.dumps(onemandata) # 序列化对象含有的属性 print(seqdata) reseqonemandata = json.loads(seqdata) # 反序列化 print(reseqonemandata)
有些脑筋转不过圈的同学可能会想,那序列化的对象如果有函数怎么办? 你只需要在反序列方定义一个和序列化程序中的那个类一样的类,成员函数你当然也写出来了,自然可以调用。
''' 序列化方代码 ''' import json class Human(object): def __init__(self, name, age, sex): self.name = name self.age = age self.sex = sex def myfunc(self): print(self.name) def dict2human(d): return Human(d['name'], d['age'], d['sex']) oneman = Human('如花', 18, "女") onemandata = {"name": oneman.name, "age": oneman.age, "sex": oneman.sex} seqdata = json.dumps(onemandata) # 序列化对象含有的属性 print(seqdata) ''' 反序列化方代码 ''' class Human(object): def __init__(self, name, age, sex): self.name = name self.age = age self.sex = sex def myfunc(self): print(self.name) reseqonemandata = json.loads(seqdata, object_hook=dict2human) # 反序列化成 Human 类的对象 reseqonemandata.myfunc() # 完全没问题
熟练使用 pickle 模块
熟练使用 json 模块。
知道 pickle 和 json 的区别
看以下代码,想想结果为什么。
import json data = "python" print(len(data)) print(len(json.dumps(data)))
celery现在不推荐pickle,不过之前还不知道是通过save load来传递参数的,我以为是rpc呢