序列化和反序列化

1. 序列化和反序列化需求的提出

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

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

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

凡是要将数据保存下来或者将数据进行网络传输,就需要将数据序列化

当我们通过网络传输或加载磁盘拿到一个序列后,要将序列还原回原来的数据类型,就是反序列化

持久化(保存到磁盘)之前必须先进行序列化

序列化之后要实现的就是要记录一个对象的边界、类型、和数据
定义

2. 什么是序列化和反序列化

2.1. serialization 序列化

将内存中对象存储下来,把它变成一个个字节。-> 二进制

2.2. deserialization 反序列化

  • 将文件的一个个字节恢复成内存中对象。<- 二进制
  • 序列化保存到文件就是持久化。

3. 序列化和反序列化的应用场景

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

1 一般来说,本地序列化的情况,应用较少。大多数场景都应用在网络传输中。

2 将数据序列化后通过网络传输到远程节点,远程服务器上的服务将接收到的数据反序列化后,就可以使用了。 但是,要注意一点,远程接收端,反序列化时必须有对应的数据类型,否则就会报错。尤其是自定义类,必须远程得有一致的定义。

3 现在,大多数项目,都不是单机的,也不是单服务的,需要多个程序之间配合。需要通过网络将数据传送到其他节点上去,这就需要大量的序列化、反序列化过程。但是,问题是,Python程序之间还可以都是用pickle解决序列化、反序列化,如果是跨平台、跨语言、跨协议pickle就不太适合了,就需要公共的协议。例如XML、Json、Protocol Buffer等。

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

4. 序列化和反序列化的实现

4.1. 实现序列化和反序列化的方法

实现序列化和反序列化的方法有很多

  1. 总体可以分为两大类:基于二进制的方式基于文本的方式
  2. 但是不管基于那种方式,最终实现的都是将数据序列化为 字节
  3. 实际上数据的序列化和数据写入文本或传输都是将数据转换为一个个字节,进行存储和传输。
  4. 只不过序列化除了要写入数据本体之外还要写入很多描述性的字节,这些描述性的字节包括:数据的边界数据的类型,这些描述性的字节到底是什么,就需要查看对应的序列化协议说明文档了。

4.2. 基于二进制的序列化方案

  • pickle 库,Python 提供。
  • Protocol Buffer
  • MessagePack

4.3. 基于文本方式序列化方案有

基于文本的序列化方案是有编码的

  • XML
  • Json

4.4. 两种序列化方案的比较

二进制的序列化方案效率要高于文本的序列化方案。但是二进制的序列化方案学习难度要高于文本的序列化方案,所以做选择什么样的序列化方案要看需求

5. python中通用的序列化方法

不管是上面介绍的那种序列化技术,都支持下面的操作方法

函数 说明
dumps 对象序列化为 bytes对象(操作的是内存中的bytes)
loads 从bytes对象反序列化 (操作的是内存中的bytes)
dump 对象序列化 到文件对象,也就是存入文件 (操作的是文件)
load 对象反序列化,从文件读取数据 (操作的是文件)

6. pickle 库 (内建库,二进制形式的序列化,没有编码)

6.1. 基本数据类型的序列化和反序列化

import pickle

file = './test'

i = 99
c = 'b'
l = list('123')
d = {'a':1, 'b': 'abc', 'c': [1,2,3]}

#######序列化

# with open(file, 'wb') as f:
#     pickle.dump(i, f)
#     pickle.dump(c, f)
#     pickle.dump(l, f)
#     pickle.dump(d, f)

######反序列化
# 方法1
# with open(file, 'rb') as f:
#     print(pickle.load(f))
#     print(pickle.load(f))
#     print(pickle.load(f))
#     print(pickle.load(f))
# 方法2
with open(file, 'rb') as f:
    # print(f.read(), f.seek(0))
    for _ in range(4):
        x = pickle.load(f)
        print(x, type(x))

序列化多少的数据,就可以反序列化多少个出来,反序列化的时候,可以少一次(少提取数据),但不能多,多了就会报错

import pickle

file = './test'

i = 99
c = 'b'
l = list('123')
d = {'a':1, 'b': 'abc', 'c': [1,2,3]}

#######序列化

with open(file, 'wb') as f:
    pickle.dump(i, f)
    pickle.dump(c, f)
    pickle.dump(l, f)
    pickle.dump(d, f)

with open(file, 'rb') as f:
    # print(f.read(), f.seek(0))
    for _ in range(5): # 序列化的时候有 4 个数据,但是这里反序列化的时候执行了 5 次,抛异常
        x = pickle.load(f)
        print(x, type(x))
------------------------------------------
C:\python36\python.exe D:/pyporject/test/test7.py
99 <class 'int'>
Traceback (most recent call last):
b <class 'str'>
  File "D:/pyporject/test/test7.py", line 29, in <module>
    x = pickle.load(f)
['1', '2', '3'] <class 'list'>
EOFError: Ran out of input
{'a': 1, 'b': 'abc', 'c': [1, 2, 3]} <class 'dict'>

Process finished with exit code 1

6.2. 类的序列化和反序列化

import pickle
####### 对象序列化
class AAA:
    aaaa = 'abc'
    def show(self):
        print('ABC')

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

####### 序列化
s = pickle.dumps(a1)
print('s = {}'.format(s)) # AAA 序列化之后的字节
print('=' * 60)
####### 反序列化
a2 = pickle.loads(s)
print(a2)
print(type(a2))
print(a2.aaaa)
a2.show()
--------------------------------------------
C:\python36\python.exe D:/pyporject/test/test7.py
s = b'\x80\x03c__main__\nAAA\nq\x00)\x81q\x01.'
============================================================
<__main__.AAA object at 0x000001DA336945C0>  # 只有 AAA 没有其他的 aaaa、'abc' 等
<class '__main__.AAA'>
abc
ABC

Process finished with exit code 0

上例中,其实就保存了一个类名,因为所有的其他东西都是类定义的东西,是不变的,类就可以理解为一个模板,所以只序列化一个AAA类名。反序列化的时候通过类名就可以找到类的定义拿到类中的所有内容

import pickle
####### 对象序列化
class AAA:
    def __init__(self):
        self.aaaa = 'abcd'

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

####### 序列化
s = pickle.dumps(a1)
print('s = {}'.format(s)) # AAA 序列化之后的字节
print('=' * 60)
####### 反序列化
a2 = pickle.loads(s)
print(a2)
print(type(a2))
print(a2.aaaa)
--------------------------------------
C:\python36\python.exe D:/pyporject/test/test7.py
s = b'\x80\x03c__main__\nAAA\nq\x00)\x81q\x01}q\x02X\x04\x00\x00\x00aaaaq\x03X\x04\x00\x00\x00abcdq\x04sb.'
# 此时的可以看到序列化之后可以看到 AAA、aaaa、adcd
============================================================
<__main__.AAA object at 0x0000026C23EFD550>
<class '__main__.AAA'>
abcd

Process finished with exit code 0

这回除了必须保存的AAA,还序列化了aaaa和abc,因为这是每一个对象自己的属性,每一个对象(个体)的属性值有可能不一样,所以这些数据需要单独序列化。在pickle中,实例自己的属性需要单独序列化

6.3. 序列化、反序列化实验

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

import pickle
# 实验
# 对象序列化
class AAA:
    def __init__(self):
        self.aaaa = 'abc'
a1 = AAA() # 创建AAA类的对象

# 序列化
ser = pickle.dumps(a1)
print('ser={}'.format(ser)) # AAA aaaa abc
print(len(ser))
filename = 'o:/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('ser', 'rb') as f:
    a = pickle.load(f)
    print(a)
    a.show()
    print(a.aaaa)

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

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

7. json (文本形式的序列化) ***

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

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

7.1. Json 的数据类型


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

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

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

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

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

json的实例

{
    "person": [
        {
         "name": "tom",
         "age": 18
        },

        {
        "name": "jerry",
        "age": 16
        }
    ],
    "total": 2
}

7.2. json模块(内建模块)

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

Python 数据类型 Json 数据类型
True (一定要大写) true
False(一定要大写) false
None null
str string
int integer
float float
list array
dict object

7.3. python 中对 json 操作的常用方法

json 的操作都是基于文本的(有编码,需要注意的是:经过实验这里的编码是 unicode编码,中文是通过unicode编码双字节表示的)

import json

file = './scm.json'
with open(file, 'w', encoding='utf-8') as f:
    json.dump("test, tt, 美国", f, ensure_ascii=True)
--------./scm.json 文件的内容是----------------------------------
"test, tt, \u7f8e\u56fd"
import json

file = './scm.json'
with open(file, 'w', encoding='gb2312') as f:
    json.dump("test, tt, 美国", f, ensure_ascii=True)
--------./scm.json 文件的内容是----------------------------------
"test, tt, \u7f8e\u56fd"
import json

file = './scm.json'
with open(file, 'w', encoding='gbk') as f:
    json.dump("test, tt, 美国", f, ensure_ascii=True)
--------./scm.json 文件的内容是----------------------------------
"test, tt, \u7f8e\u56fd"
Python类型 json类型
dumps json编码,并序列化为内存中的字符序列
dump json编码,并存入文本文件
loads json解码,从内存中的字符序列反序列化为对应类型的数据
load json解码,从文本文件读取数据
import json
d = {'name':'Tom', 'age':20, 'interest':['music', 'movie']}
j = json.dumps(d)
print(j, type(j)) # 请注意引号的变化,注意数据类型的变化
d1 = json.loads(j)
print(d1)
print(id(d), id(d1))

7.4. 命令行解析json文件

示例

"""Command-line tool to validate and pretty-print JSON

Usage::

    $ echo '{"json":"obj"}' | python -m json.tool
    {
        "json": "obj"
    }
    $ echo '{ 1.2:3.4}' | python -m json.tool
    Expecting property name enclosed in double quotes: line 1 column 3 (char 2)

"""
D:\pyporject\test>python -m json.tool mysql.json
{
    "DEFAULT": {
        "a": "test"
    },
    "mysql": {
        "a": "test",
        "default-character-set": "utf8"
    },
    "mysqld": {
        "a": "test",
        "datadir": "/dbserver/data",
        "port": "3309",
        "character-set-server": "utf8",
        "sql_mode": "NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES"
    },
    "test": {
        "a": "test",
        "x": "1",
        "y": "2"
    }
}

8. MessagePack (第三方库,二进制形式的序列化) ***

8.1. MessagePack的特点

  • MessagePack是一个基于二进制高效的对象序列化类库,可用于跨语言通信。
  • 可以像JSON那样,在许多种语言之间交换结构对象。
  • 它比JSON更快速也更轻巧。
  • 支持Python、Ruby、Java、C/C++等众多语言。宣称比Google Protocol Buffers还要快4倍。
  • 兼容 json和pickle。
  • MessagePack简单易用,高效压缩,支持语言丰富。所以,用它序列化也是一种很好的选择。Python很多大名鼎鼎的库都是用了msgpack。

8.2. MessagePack的安装

pip install msgpack

8.3. MessagePack的常用方法

  • packb 序列化对象。提供了dumps来兼容pickle和json。
  • unpackb 反序列化对象。提供了loads来兼容。
  • pack 序列化对象保存到文件对象。提供了dump来兼容。
  • unpack 反序列化对象保存到文件对象。提供了load来兼容。
import pickle
import msgpack
import json
d = {'person': [{'name': 'tom', 'age': 18}, {'name': 'jerry', 'age': 16}], 'total': 2}
# json
data = json.dumps(d)
print(type(data), len(data), data)
print(data.replace(' ', ''))
print(len(data.replace(' ', ''))) # 72 bytes 注意这样替换的压缩是不对的
# pickle
data = pickle.dumps(d)
print(type(data), len(data), data) # 101 bytes
# msgpack
data = msgpack.dumps(d)
print(type(data), len(data), data) # 48 bytes
print('-' * 30)
u = msgpack.unpackb(data)
print(type(u), u)
print(msgpack.loads(data))
u = msgpack.loads(data, encoding='utf-8')
print(type(u), u)

9. JS中对json数据的序列化和反序列化

  • JSON.stringify(obj) 将JS对象
var obj = {'name': "小霞", 'age': 23}

console.log(1, obj, typeof(obj))
obj1 = JSON.stringify(obj)
console.log(2, obj1, typeof(obj1))
obj2 = JSON.parse(obj1)
console.log(3, obj2, typeof(obj2))

var lst = [1, 2, 3]
console.log(4, lst, typeof(lst))
lst2 = JSON.stringify(lst)
console.log(5, lst2, typeof(lst2))
lst3 = JSON.parse(lst2)
console.log(6, lst3, typeof(lst3))


// 结果
1 { name: '小霞', age: 23 } 'object'
2 '{"name":"小霞","age":23}' 'string'
3 { name: '小霞', age: 23 } 'object'
4 [ 1, 2, 3 ] 'object'
5 '[1,2,3]' 'string'
6 [ 1, 2, 3 ] 'object'

10. 使用json序列化python中类的实例

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# __Author: MING


import json as default_json
from json.encoder import JSONEncoder

class A:
    x = 100
    y = 200

    def __init__(self):
        self.m = 1111
        self.n = 2222

    def show(self):
        print('ok')


a = A()
# cls_a1 = default_json.dumps(a)  // 使用默认的json是不能够序列化示例对象的。


class MyJson(JSONEncoder):
    def default(self, o):
        if isinstance(o, A):
            return o.__dict__
        return JSONEncoder.default(self, o)


class Json:
    @staticmethod
    def dumps(data, ensure_ascii=True):
        return default_json.dumps(data, ensure_ascii=ensure_ascii, cls=MyJson)

cls_a2 = Json.dumps(a)  # 重写default方法后,可以使用自定义的json序列化示例对象了。

print(cls_a2)

11. 序列化方案的选择

  • 首先选择使用文本的序列化方案还是二进制的序列化方案
  • 其次看看要不要跨平台
  • 在序列化方案类型中选择一款自己平台可以使用的,对反平台也兼容的序列化方案
  • pickle 仅在python中使用,我们仅仅拿来理解二进制序列化和序列化到二进制的过程,除了pickle,其他的库都支持跨平台、跨语言的。
posted @ 2018-03-14 23:46  scm1911  阅读(417)  评论(0编辑  收藏  举报
/** ####################################################################### */