Python3学习(32)--序列和反序列化(一)

本篇,我将分为两个章节来讲一下,什么是Python 的序列化,以及Python的序列化在不同语言之间的应用(第二篇将会讲到),当然,有正就有负,有左就要有右,有上就要有下,等等,事物的存在都是有两面性的,因此,讲Python的序列化的时候,就不得不讲到反序列化,如果将这两个概念比喻成行为的话,那么就相当于一个在包装,一个在分解。



序列化


我们举个简单的例子,就拿1+1来说吧,看下如下代码


#!/usr/bin/env Python3
# -*- coding:utf-8 -*-

a = 1+1
print(a)

a的结果显然等于2,且这个结果属于运行时才计算出来的,存放在了a对应的内存地址中。


Python3学习(32)--序列和反序列化(一)_第1张图片


我们一旦程序结束,就访问不到这个a变量了(假设,我们希望a的值是永驻内存的,因此我们试图直接从内存中调出a变量的值)


Python3学习(32)--序列和反序列化(一)_第2张图片



其实我们就是想再看一下1+1的结果,可是,内存中好像并没有这个结果,我们以为a还在内存中,但是,内存毕竟有限,不可能你不用的时候,还给你留一块house,专门供a这个变量长住,如果这样的话,内存会被耗死。因此,我们的程序一旦结束,内存就会把a这个变量有关的一切,全部清掉(记忆抹除),如果,我们想要再次看到1+1的结果,就得在内存中重新分配a的地址,重新计算,重新赋值。有没有其他的办法,可以让1+1的结果,一次运行,处处调用,且不再考虑内存地址的重新分配呢?


这个时候,我们就需要借助序列化这个概念,将变量或对象通过序列化,转化成字节流(bytes)存储到文件中(磁盘)或者通过网络传输到需要的机器上。


因此,我们把变量或对象从内存中变成可存储或传输的过程称之为序列化


在Python中,有两种提供序列化的模块,一个是pickle,一个是json,前者,序列化变量或对象成字节流,但却是Python独有的模块,序列化后的字节流,无法和其他语言进行交互(第二篇的时候,会和Java做个交互,验证一下,而且这也是其他编程语言特有的序列化问题,而且,如果Python版本不一致,也有可能出现序列化和反序列化互不兼容的情况发生),后者,可将对象序列化成网络中标准的数据传输格式---->json串,这是一个轻量级的数据交互格式,相对于XML来说,简洁且高效。



序列化之  ---- pickle模块


pickle模块提供了四个功能:dumps、dump、loads、load。(json模块和pickle提供的一样,但是使用起来还是稍微有点差别)

序列化

dumps : 直接将变量或对象序列化成bytes,可以用print打印内容
dump   : 将变量或对象序列化后的内容写入指定的文件

反序列化

loads  : 直接将序列化后的字节流,反序列化(加载,还原)成序列化之前的状态,可以用print打印内容
load    : 将一个序列化后的文件反序列化(加载,还原)成变量或者对象


下面,我们就来实际操作一下,实现变量或对象的序列化和反序列化,两种模式+两种方式:



A、文件模式  dump 和 load 的组合


序列化对象的内容,并写入文件


pickling.py

#!/usr/bin/env Python3
# -*- coding:utf-8 -*-

import pickle

d = {'name':'appleyk','sex':'男','age':26}
with open('info.txt','wb') as f:
    pickle.dump(d,f)


运行后,我们查看一下,info.txt的内容(注意,如果第一次open文件,权限请使用w,如果写入的内容是字节流,w权限后面跟b)




一堆乱码,完全看不懂!反序列化文件info.txt里面的bytes为dict对象,并打印集合内容,还原真相


unpickling.py

#!/usr/bin/env Python3
# -*- coding:utf-8 -*-

import pickle

#d = {'name':'appleyk','sex':'男','age':26}
#with open('info.txt','wb') as f:
    #pickle.dump(d,f)
with open('info.txt','rb') as f:
    d = pickle.load(f)
    print(d)

Python3学习(32)--序列和反序列化(一)_第3张图片


B、传输模式  dumps 和 loads 的组合

(1)本地反序列化


#!/usr/bin/env Python3
# -*- coding:utf-8 -*-

import pickle

a = 1+1 

byte = pickle.dumps(a)        #序列化a的结果  --->字节流 
print(byte)

result = pickle.loads(byte)   #反序列化字节流 --->a的结果
print(result)



注意,变量result和之前的变量a是两个毫不相干的对象,但是,他们的内容都是一样的,结果都是2

(2)网络传输反序列化(仅限Python语言,关于和Java语言的交互,放在下一篇说明)


服务端server.py

#!/usr/bin/env Python3
# -*- coding:utf-8 -*-

import socket ,pickle

serSock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
serSock.bind(('192.168.1.54',9999))
serSock.listen(5)
print('等待连接......')
s,addr = serSock.accept()

result = s.recv(1024)    #来至于客户端的 消息

print('客户端:',result)	
a = pickle.loads(result) #反序列化 

print('反序列化后,a = ',a)
s.send(bytes('服务端:消息接收完毕!',encoding='utf-8'))
serSock.close;           #最后别忘关了套接字对象,关闭资源
  


启动服务端


Python3学习(32)--序列和反序列化(一)_第4张图片



客户端client.py


#!/usr/bin/env Python3
# -*- coding:utf-8 -*-
import pickle,socket

a = 1+1 
byte = pickle.dumps(a)        #序列化a的结果  --->字节流 

s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(('192.168.1.54 ',9999))  
s.send(byte)
print(s.recv(1024).decode('utf-8'))
s.close()


由于,服务端已经在监听了,客户端启动后,两边唰的一下完成数据传输,随后关闭各自的套接字,此过程中,客户端完成序列化,服务端完成反序列化,注意两边的bytes的编码一定要保持一致!效果如下


服务端:


Python3学习(32)--序列和反序列化(一)_第5张图片

客户端:


Python3学习(32)--序列和反序列化(一)_第6张图片


关于套接字,我们依然在本篇中略过,因为,网络socket通信,涉及多线程,因此,我们统一放在后续的连载博文中再讲!


当然,这个是针对变量序列化的,我们也可以针对对象,进行序列化和反序列化,我们快速看下demo和效果演示


服务端 server.py

#!/usr/bin/env Python3
# -*- coding:utf-8 -*-

import socket ,pickle

class Student(object):  #一定要声明下这个Student类,否则反序列化出对象的时候,找不到类标志
    pass

serSock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
serSock.bind(('192.168.1.54',9999))
serSock.listen(5)
print('等待连接......')
s,addr = serSock.accept()

result = s.recv(1024)    #来至于客户端的 消息

print('客户端:',result)	

stu = pickle.loads(result) #反序列化 

print(stu.name,stu.sex,stu.age)
s.send(bytes('服务端:消息接收完毕!',encoding='utf-8'))
serSock.close;           #最后别忘关了套接字对象,关闭资源
  

客户端 client.py

#!/usr/bin/env Python3
# -*- coding:utf-8 -*-
import pickle,socket

class Student(object):
    def __init__(self,name,sex,age):
        self.name = name
        self.age  = age
        self.sex  = sex
    def show(self):
        print(self.name,self.sex,self.age)
s = Student('appleyk','男',26)
byte = pickle.dumps(s)        #序列化实例s  --->字节流 
stu  = pickle.loads(byte)
stu.show() #注意,方法是无法通过传输,进行反序列化的,感兴趣的可以自己验证下
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(('192.168.1.54 ',9999))  
s.send(byte)
print(s.recv(1024).decode('utf-8'))
s.close()

演示效果


服务端


Python3学习(32)--序列和反序列化(一)_第7张图片



客户端


Python3学习(32)--序列和反序列化(一)_第8张图片


因此,我们在将序列化应用到网络传输的时候,pickle只能保存一些基本数据类型(这就够用了,毕竟我们主要是传数据的,不是传递行为的),因此,如果一些对象中的成员,传输后,无法被成功的反序列化,请不要大惊小怪,毕竟,对象的行为操作,是有域的,如果离开了自身所处的环境,其他地方是无法正常运转的,就好比,淡水中的鱼,我们可以拿来享用,但是,如果,我们把它放在咸水中饲养一段时间后再享用,肯定是不行的!它会挂掉的。



序列化之  ---- json模块


讲之前,我们先来看一下,json数据类型和python数据类型之间的对照关系


JSON 类型      Python 类型
     {}      dict
     []                      list
  "string"                str
  1234.56         int 或 float
  true/false       True/False

    null                  None


我们有个疑问,Python中的类对象(实例),对应JSON中的哪个类型呢?先不管这个,我们还是先来实际操作一下,看看利用json模块是如何来序列化我们的变量的(PS:网络传输这个环节,我们就不演示了,放在下一篇再讲)

json模块也提供了四个功能:dumps、dump、loads、load。

和pickle模块提供的功能几乎一样,这里不再做说明。

A、序列化dict类型

(1)dump和load组合

序列化后的内容写入指定文件(注意,json是一个字符串,不要当成bytes来写入了,区分pickle)

jsoning

#!/usr/bin/env Python3
# -*- coding:utf-8 -*-

import json

d = {'name':'appleyk','sex':'男','age':26}
with open('json.txt','w') as f:
    json.dump(d,f)

运行demo,并核对下json.txt里面的内容




中文乱码(unicode码),不要慌好吧,反序列的时候,我们会还原真相的!


unjsoning

#!/usr/bin/env Python3
# -*- coding:utf-8 -*-

import json

#d = {'name':'appleyk','sex':'男','age':26}
#with open('json.txt','w') as f:
    #json.dump(d,f)
with open('json.txt','r') as f:
    jsonObject = json.load(f)
    print(jsonObject)
    print(jsonObject['name'])

执行demo,效果如下

Python3学习(32)--序列和反序列化(一)_第9张图片

(2)dumps和loads组合

demo.py

#!/usr/bin/env Python3
# -*- coding:utf-8 -*-

import json

d = {'name':'appleyk','sex':'男','age':26}

jstr = json.dumps(d)
print(jstr)
print('-----上面为json的序列化,下面为json的反序列化-----')
jsonObject = json.loads(jstr)
print(jsonObject)


Python3学习(32)--序列和反序列化(一)_第10张图片


B、序列化int类型(基本数据类型的演示,到此为止,其余类型,都是一个模子里刻出来的)


import json

#d = {'name':'appleyk','sex':'男','age':26}
num = 100
jstr = json.dumps(num)
print(jstr)
print('-----上面为json的序列化,下面为json的反序列化-----')
jsonObject = json.loads(jstr)
print(jsonObject)

int还是int,序列化之前和反序列化之后,没什么变化

Python3学习(32)--序列和反序列化(一)_第11张图片



C、序列化类对象

哎哟,上面我们说过了,json和python的数据类型对照表中,没有涉及到Python类的对象是怎么序列化成json类型的对象的,那么我们用序列化基本数据类型的方法来序列化一个类对象,可行吗?我们看一下


我们先来定义一个类,Employee,员工类,注意,类中有一个成员变量age,它是一个私有private类型,因此,我们需要给私有age变量提供公有属性getter和setter,我们利用之前学过的内置装饰器@property,来完成这一壮举,为什么要定义一个私有的age,目的就是为了区分和对比public和private的成员变量,在默认序列化成json对象的时候的细微不同之处。


Employee类定义如下

#!/usr/bin/env Python3
# -*- coding:utf-8 -*-

import json

class Employee:
    def __init__(self,name,sex,age,salary):
        self.name  = name    #姓名
        self.sex   = sex     #性别 
        self.__age = age     #年龄 私有变量 
        self.salary= salary  #薪资 按月算    
    @property
    def age(self):            #age的getter属性,还记得装饰器@property的用法吗?
        return self.__age
    @age.setter
    def age(self,value):      #age的setter属性
        self.__age = value

实例化一个对象person

person = Employee('张三','男',35,4560.00)

我们利用序列化基本数据类型的方法dumps,来试图,序列化这个person对象,操作如下

print(json.dumps(person))


打印结果的时候,IDE抛出了异常,异常信息如下




意思就是,这个类型为Employee的对象,不支持JSON序列化,换句话说,你这个person对象,究竟让我json给你怎么个序列化法呢?你是不是需要指定一种格式(这种格式区分于基本数据类型),告诉json的dumps方法,嘿,伙计,我给你一个person对象,这是参考格式,照着这个给我序列化一下。


这个格式是什么呢?那我们就得借助help函数,来看一下,json.dumps()的信息了


Python3学习(32)--序列和反序列化(一)_第12张图片


我们发现,虽然dumps函数的参数很多,但是必须的只有一个,那就是第一个参数obj,也就是要序列化的对象,一定要有,其余的参数都可以使用默认值,但是


我们接着往下看(Enter键往下翻),直奔主题


 ``default(obj)`` is a function that should return a serializable version
 of obj or raise TypeError. The default simply raises TypeError.




意思就是,关键字参数default使用默认值(None)的时候,是针对当前对象obj(第一个参数)的,如果当前对象,可以被(支持)序列化,那就返回对象的可序列化版本(比如int就是int,{}就是dict,true就是True),另一种就是,不支持的时候,就会抛出TypeError的异常,所以,我们看到的builtins.TypeError: Object of type 'Employee' is not JSON serializable这句异常信息,实际上就是这个default引起的,我们既然没有类的对象对应的json序列化版本,那我们就去创造一个,它不是说了嘛,a function that that should return a serializable version of obj,那我们就为default指定一个函数:


function of ToJson


def ToJson(obj):
    return {
    'name'  : obj.name,
    'sex'   : obj.sex ,
    'age'   : obj.age ,
    'salary': obj.salary   
    }

print(json.dumps(person,default=ToJson))  

这样一来,函数dumps在序列化对象person的时候,就会按照ToJson函数的模板去构造属于person对象的json串,不相信吗,我们看一下执行结果好吧:

Python3学习(32)--序列和反序列化(一)_第13张图片


如果我们构造了三个Employee的实例怎么办,别慌,我们最开始不是说了json和python的基本数据类型对应关系吗,Pyhton的list集合对应json的[](数组),那我们就用Python的list来存放这三个实例,然后,将list作为要序列化的obj


person1 = Employee('张三','男',35,4560.00)
person2 = Employee('李婷','女',28,3000.00)
person3 = Employee('王五','男',45,7000.00)

PList = []
PList.append(person1)
PList.append(person2)
PList.append(person3)

def ToJson(obj):
    return {
    "name"  : obj.name,
    "sex"   : obj.sex ,
    "age"   : obj.age ,
    "salary": obj.salary   
    }

print(json.dumps(PList))

注意,因为PList中的元素是类类型的对象,因此,参数default如果不指定序列化转换规则的话,仍然会让dumps函数一脸懵逼,上面的demo在运行时抛出异常:


Python3学习(32)--序列和反序列化(一)_第14张图片



ok,我们也不为难dumps函数了,正常点好了


Python3学习(32)--序列和反序列化(一)_第15张图片


拿到这个JSON串后,你就可以对数据进行解析了,排版后的格式如下


Python3学习(32)--序列和反序列化(一)_第16张图片


序列化类对象成json串,还有一种通用的方式,就是使用lambda表达式,来指定default的值,因为类的对象有一个属性__dict__,该属性以dict键值对的形式存储过了对象的变量名和其对应的值,如下


Python3学习(32)--序列和反序列化(一)_第17张图片



我们,看一下,利用通用的方式怎么来写


Python3学习(32)--序列和反序列化(一)_第18张图片



说了半天json串的序列化,还没有讲如何将json串反序列化成对象,不能讲太细了,关于loads函数信息,借助help函数自行查看,既然,我们dumps函数指定了序列化的转换函数,那么,我们的loads函数也要指定一下反序列化的转换函数,注意,前者是将对象按照指定的函数转化成json串,后者则是这项工作的逆向操作


直接上完整demo


#!/usr/bin/env Python3
# -*- coding:utf-8 -*-

import json

class Employee:
    def __init__(self,name,sex,age,salary):
        self.name  = name  ;   #姓名
        self.sex   = sex   ;  #性别 
        self.__age = age   ;  #年龄 私有变量 
        self.salary= salary;  #薪资 按月算    
    @property
    def age(self):            #age的getter属性,还记得装饰器@property的用法吗?
        return self.__age
    @age.setter
    def age(self,value):      #age的setter属性
        self.__age = value

person1 = Employee('张三','男',35,4560.00)
person2 = Employee('李婷','女',28,3000.00)
person3 = Employee('王五','男',45,7000.00)

PList = []
PList.append(person1)
PList.append(person2)
PList.append(person3)

def ToJson(obj):
    return {
    "name"  : obj.name,
    "sex"   : obj.sex ,
    "age"   : obj.age ,
    "salary": obj.salary   
    }

def ToObject(d):
    return Employee(d['name'],d['sex'],d['age'],d['salary'])
jstr = json.dumps(PList,default=ToJson) #高阶函数
print('{ "data":'+jstr+'}')
#object_hook指向一个自定义解码器(函数),解码器的对象就是json串中的{},结果对应Python的dict
#一层层的剥离出{},向Python的dict靠拢
L  = json.loads(jstr,object_hook=ToObject) 
print(L)
for obj in L:
    print(obj,'----->',obj.name)


我们看下最终效果


Python3学习(32)--序列和反序列化(一)_第19张图片




你肯定会有这样一个疑问,如果单单是传送基本数据的话,那我直接写一个函数,将对方要的数据封装成str不就行了嘛,干嘛还要用json模块序列化功能呢,我去,如果你要这样想,我拿你没办法,Python给你提供的有现成的模块你不用,你非要自己去写,好吧,那你就一个数据一个数据的拼接去吧。


下一篇,我们转向实际应用开发,讲解一下,如何利用Python现成的json模块提供的序列化json串的功能,来实现,和Java的网络数据通信!



你可能感兴趣的:(Pyhon3.X学习,Python3,序列和反序列,Python3,对象序列化为JSON串,Python,序列与反序列化,Python序列化详解)