python-序列化和反序列化

小丫4个月前技术文章118

1、为什么要序列化


内存中的字典、列表、集合以及各种对象,如何保存到一个文件中?如果是自己定义的类的实例,如何保存到一个文件中?


如何从文件中读取数据,并让它们在内存中再次恢复成自己对应的类的实例?


要设计一套协议,按照某种规则,把内存中数据保存到文件中。文件是一个字节序列,所以必须把数据转换成字节序列,输出到文件。这就是序列化。反之,从文件的字节序列恢复到内存并且还是原来的类型,就是反序列化。


2、定义


serialization 序列化
将内存中结构、对象等存储至磁盘,把它变成一个个字节 ——> 二进制


deserialization 反序列化
将文件的一个个字节恢复成内存中对象 <—— 二进制


序列化保存到文件就是持久化。
可以将数据序列化后持久化,或者网络传输;也可以将从文件中或者网络接收到的字节序列反序列化。


Python 提供了 pickle 库。


3、pickle库


Python 中的序列化、反序列化模块。

import pickle

filename = "F:/demo/ser"

# 序列化后看到什么?
a = 97    # 0x61
b = 99    # 0x63
c = 'c'   # 0x63 ——> 'c'和99的十六进制一样,序列化如何保存数据类型?
# 序列化会将数据类型、数据、边界等等都进行序列化保存,以便反序列化能够正常读取
d = [48, 'abc']    # 0x30  0x61 0x62 0x63
e = {'aaaaa': 'ddd', 'bbbbb': 1, 'ccccc': 0, 'eeeee':'11111'}

# 序列化
with open(filename, 'wb') as f:
    pickle.dump(a, f)    # 将对象序列化到文件
    pickle.dump(b, f)
    pickle.dump(c, f)
    pickle.dump(d, f)
    pickle.dump(e, f)
# 查看本文文件和二进制文件发现:序列化会将数据类型、数据、边界等等都进行序列化保存

# 反序列化
with open(filename, 'rb') as f:
    # 序列化几次就要反序列化几次
    for i in range(5):
        x = pickle.load(f)
        print(type(x), x)
        if isinstance(x, list):
            print(id(x), id(d))    # id不一样,序列化和反序列化前后不是同一个数据

# 执行结果:
<class 'int'> 97
<class 'int'> 99
<class 'str'> c
<class 'list'> [48, 'abc']
2553352971016 2553351787720
<class 'dict'> {'aaaaa': 'ddd', 'bbbbb': 1, 'ccccc': 0, 'eeeee': '11111'}


import pickle

# 对象序列化
class AAAA:
    bbbb = 'ABC'
    def cccc(self):
        print('dddd')

a1 = AAAA()    # 创建AAAA类的对象

# 序列化
ser = pickle.dumps(a1)       # 将对象序列化成bytes对象
print("ser={}".format(ser))  # ser=b'\x80\x03c__main__\nAAAA\nq\x00)\x81q\x01.'

# 反序列化
a2 = pickle.loads(ser)
print(a2.bbbb)    # ABC,反序列化后调用
a2.cccc()         # dddd,反序列化后调用


上面的例子中,故意使用了连续的 AAAA、ABC、dddd 等字符串,就是为了在二进制文件中能容易的发现它们。


上例中,其实就保存了一个类名,因为所有的其他东西都是类定义的东西,是不变的,所以只序列化一个 AAAA 类名。反序列化的时候找到类就可以恢复一个对象。


import pickle

# 对象序列化
class AAAA:
    def __init__(self):
        self.bbbb = 'abc'

a1 = AAAA()    # 创建AAAA类的对象

# 序列化
ser = pickle.dumps(a1)       # 将对象序列化成bytes对象
print("ser={}".format(ser))  # ser=b'\x80\x03c__main__\nAAAA\nq\x00)\x81q\x01}q\x02X\x04\x00\x00\x00bbbbq\x03X\x03\x00\x00\x00abcq\x04sb.'

# 反序列化
a2 = pickle.loads(ser)
print(type(a2), a2)   # <class '__main__.AAAA'> <__main__.AAAA object at 0x0000019BE8C1F948>
print(a2.bbbb)        # abc
print(id(a1), id(a2)) # 1769136583432 1769136585032


可以看出这回除了必须保存的 AAAA,还序列化了 bbbb 和 abc,因为这是每一个对象自己的属性,每一个对象不一样的,所以这些数据需要序列化。


3.1 序列化、反序列化实验

定义类 AAA,并序列化到文件

import pickle

# 实验
# 对象序列化
class AAA:
    def __init__(self):
        self.aaaa = 'abc'

a1 = AAA()    # 创建AAA类的对象

# 序列化
ser = pickle.dumps(a1)
print("ser={}".format(ser))    # ser=b'\x80\x03c__main__\nAAA\nq\x00)\x81q\x01}q\x02X\x04\x00\x00\x00aaaaq\x03X\x03\x00\x00\x00abcq\x04sb.'
print("序列化后的字节长度:{}".format(len(ser)))    # 序列化后的字节长度:49

filename = "F:/ser"

with open(filename, 'wb') as f:
    pickle.dump(a1, f)


将产生的序列化文件 ser 发送到其他节点上。


增加一个 x.py 文件,内容如下。最后执行这个脚本 $ python x.py


import pickle

with open('ser', 'rb') as f:
	a = pickle.load(f)    # 异常!


会抛出异常 AttributeError: Can't get attribute 'AAA' on <module '__main_' from 't.py'>


这个异常实际上是找不到类 AAA 的定义,增加类定义即可解决。


反序列化的时候要找到 AAA 类的定义,才能成功。否则就会抛出异常。


可以这样理解:反序列化的时候,类是模子,二进制序列就是铁水。


import pickle

class AAA:
	def show(self):
		print('xyz')

with open('F:/ser', 'rb') as f:
	a = pickle.load(f)
	print(a)       # <__main__.AAA object at 0x0000025B9669F2C8>
	a.show()       # xyz
	print(a.aaaa)  # abc


这里定义了类 AAA,并且上面的代码也能成功的执行。


注意:这里的 AAA 定义和原来完全不同了。
因此,序列化、反序列化必须保证使用同一套类的定义,否则会带来不可预料的结果。


4、序列化应用

一般来说,本地序列化的情况,应用较少。大多数场景都应用在网络传输中。
将数据序列化后通过网络传输到远程节点,远程服务器上的服务将接收到的数据反序列化后,就可以使用了。但是,要注意一点,远程接收端,反序列化时必须有对应的数据类型,否则就会报错。尤其是自定义类,必须远程得有一致的定义。


现在,大多数项目,都不是单机的,也不是单服务的,需要多个程序之间配合。需要通过网络将数据传送到其他节点上去,这就需要大量的序列化、反序列化过程。


但是,问题是,Python 程序之间还可以都是用 pickle 解决序列化、反序列化,如果是跨平台、跨语言、跨协议 pickle 就不太适合了,就需要公共的协议。例如 XML、Json、Protocol Buffer 等。


不同的协议,效率不同、学习曲线不同,适用不同场景,要根据不同的情况分析选型。


5、Json

JSON(JavaScript Object Notation, JS对象标记) 是一种轻量级的数据交换格式。它基于 ECMAScript (w3c组织制定的 JS 规范) 的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。


5.1 Json 的数据类型

双引号引起来的字符串,数值,true,false,null,对象,数组,这些都是值


字符串:
由双引号包围起来的任意字符的组合,可以有转义字符。


数值:
有正负,有整数、浮点数。


对象:
无序的键值对的集合,key 必须是一个字符串,需要双引号包围这个字符串,value 可以是任意合法的值。
格式:{key1:value1, ..., keyn:valuen}



数组:
有序的值的集合。
格式:[val1, ..., valn]



实例:


{
    "person": [
        {
            "name": "Tom",
            "age": 18
        },
        {
            "name": "Jerry",
            "age": 17
        }
    ],
    "total": 2
}


5.2 json模块
5.2.1 Python与Json

Python 支持少量内建数据类型到 Json 类型的转换。


5.2.2 常用方法
import json

d = {'a': 1, 'b': 'abc', 'c': ['1', '2'], 'd': {'e': 50, 'f':(1, 2, 3)}}

j = json.dumps(d)
print(type(d), type(j))    # <class 'dict'> <class 'str'>
# 单引号转成了json要求的双引号,元组被认为是列表,set不能json序列化会报错!
print(j)    # {"a": 1, "b": "abc", "c": ["1", "2"], "d": {"e": 50, "f": [1, 2, 3]}}


一般 json 编码的数据很少落地,数据都是通过网络传输。传输的时候,要考虑压缩它。本质上来说它就是个文本,就是个字符串。
json 很简单,几乎编程语言都支持 Json,所以应用范围十分广泛。


5.3 simplejson模块

第三方模块,需要下载:pip install simplejson


import json
import simplejson

d = {'class1': {'张三': 12, '李四': 13},
     'class2': {'王五': 15, '赵六': 12},
     'class3': {'孙七': 14, '胡八': 13}}

j = json.dumps(d)
print(type(j), j)
s = simplejson.dumps(d)
print(type(s), s)

6、MessagePack

官网:https://msgpack.org/


# 需要安装
pip install msgpack


MessagePack 是一个基于二进制高效的对象序列化类库,可用于跨语言通信。


它可以像 JSON 那样,在许多种语言之间交换结构对象。


但是它比 JSON 更快速也更轻巧。


支持Python、Ruby、Java、C/C++ 等众多语言。宣称比 Google Protocol Buffers 还要快4倍。兼容 json 和 pickle。

import pickle
import json
import msgpack

d = {'a': 1, 'b': 'abc', 'c': ['1', '2'], 'd': {'e': 50, 'f':(1, 2, 3)}}

p = pickle.dumps(d)
print(len(p))    # 105
print(p)

j = json.dumps(d)
print(len(j))    # 69
print(j)

m = msgpack.dumps(d)
print(len(m))    # 29
print(m)

# 执行结果:
105
b'\x80\x03}q\x00(X\x01\x00\x00\x00aq\x01K\x01X\x01\x00\x00\x00bq\x02X\x03\x00\x00\x00abcq\x03X\x01\x00\x00\x00cq\x04]q\x05(X\x01\x00\x00\x001q\x06X\x01\x00\x00\x002q\x07eX\x01\x00\x00\x00dq\x08}q\t(X\x01\x00\x00\x00eq\nK2X\x01\x00\x00\x00fq\x0bK\x01K\x02K\x03\x87q\x0cuu.'
69
{"a": 1, "b": "abc", "c": ["1", "2"], "d": {"e": 50, "f": [1, 2, 3]}}
29
b'\x84\xa1a\x01\xa1b\xa3abc\xa1c\x92\xa11\xa12\xa1d\x82\xa1e2\xa1f\x93\x01\x02\x03'


可以看出,大大的节约了空间。

6.1 常用方法

packb 序列化对象。提供了 dumps 来兼容 pickle 和 json。
unpackb 反序列化对象。提供了 loads 来兼容。


pack 序列化对象保存到文件对象。提供了 dump 来兼容。
unpack 反序列化对象保存到文件对象。提供了 load 来兼容。


import msgpack

d = {'a': 1, 'b': 'abc', 'c': ['1', '2'], 'd': {'e': 50, 'f':(1, 2, 3)}}

m1 = msgpack.packb(d)
m2 = msgpack.dumps(d)
print(m1, m2, sep='\n')

m11 = msgpack.unpackb(m1)
m22 = msgpack.loads(m2)
print(m11, m22, sep='\n')

# 执行结果:
b'\x84\xa1a\x01\xa1b\xa3abc\xa1c\x92\xa11\xa12\xa1d\x82\xa1e2\xa1f\x93\x01\x02\x03'
b'\x84\xa1a\x01\xa1b\xa3abc\xa1c\x92\xa11\xa12\xa1d\x82\xa1e2\xa1f\x93\x01\x02\x03'
{'a': 1, 'b': 'abc', 'c': ['1', '2'], 'd': {'e': 50, 'f': [1, 2, 3]}}
{'a': 1, 'b': 'abc', 'c': ['1', '2'], 'd': {'e': 50, 'f': [1, 2, 3]}}


MessagePack 简单易用,高效压缩,支持语言丰富。


所以,用它序列化也是一种很好的选择。Python 很多大名鼎鼎的库都是用了 msgpack。



相关文章

Kafka 手动调整分区副本存储

Kafka 手动调整分区副本存储

              在生产环境中,每台服务器的配置和性能不一致,但是Kafka只会根据自己的代码规则创建对应的分区副本,就会导致个别服务器存储压力较大。所有需要手动调整分区副本的存储。测试:创...

MySQL运维实战之备份和恢复(8.8)恢复单表

xtrabackup支持单表恢复。如果一个表使用了独立表空间(innodb_file_per_table=1),就可以单独恢复这个表。1、Prepareprepare时带上参数--export,xtr...

GTID 模式 - 通过跳过事务解决主从故障

一、前言很多场景下我们需要跳过一个事务来修复主从关系,例如主从事务不一致,或者对无主键表更新,导致较大延迟,操作过程在此记录。二、操作流程1. 获取最后一个 GTID 操作在 GTID 模式下,如果需...

Logstash迁移ES集群

一、背景介绍   logstash 支持从一个 ES 集群中读取数据然后写入到另一个 ES 集群,因此可以使用 logstash 进行数据迁移,使用 logstash 进行迁移前,需要注意以下几点:需...

trino开启https

trino开启https

一、生成https证书(所用到的openssl和keytool命令都是linux自带的)配置https证书:(1)创建目录[hdfs@hadoop01 hadoop]# mkdir -p /data/...

Yarn调度器对比

1)Hadoop调度器重要分为三类:FIFO 、Capacity Scheduler(容量调度器)和Fair Sceduler(公平调度器)。Apache默认的资源调度器是容量调度器;CDH...

发表评论    

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。