August Rush

一个还在努力成长的小火汁!

游龙当归海,海不迎我自来也。

We create our own demons.

You can reach me at augustrush0923@gmail.com
Python数据序列化方法
发布:2021年07月28日 | 作者:augustrush | 阅读量: 252

序列化/反序列化

将对象转换为可通过网络传输或可以存储到本地硬盘的数据格式的过程称为序列化;反之,则称为反序列化。


序列化的目的

  1. 以某种存储形式使自定义对象持久化;
  2. 将对象从一个地方传递到另一个地方;
  3. 使程序更具维护性。


json模块

大部分编程语言都会提供处理json数据的接口,Python 2.6开始加入了json模块,且把它作为一个内置模块提供。


序列化与反序列化

json模块提供了以下两个方法来进行序列化和反序列化操作:

# 序列化:将Python对象转换成json字符串
dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True, 
      allow_nan=True, cls=None, indent=None, separators=None, 
      default=None, sort_keys=False, **kw)

# 反序列化:将json字符串转换成Python对象
loads(s, encoding=None, cls=None, object_hook=None, parse_float=None, 
      parse_int=None, parse_constant=None, object_pairs_hook=None, **kw)

除此之外,json模块还提供了两个额外的方法允许我们直接将序列化后得到的json数据保存到文件中,以及直接读取文件中的json数据进行反序列化操作:

# 序列化:将Python对象转换成json字符串并存储到文件中
dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True,
     allow_nan=True, cls=None, indent=None, indent=None, 
     separators=None, default=None, sort_keys=False, **kw)

# 反序列化:读取指定文件中的json字符串并转换成Python对象
load(fp, cls=None, object_hook=None, parse_float=None, parse_int=None, 
     parse_constant=None, object_pairs_hook=None, **kw)


实例

# 序列化
json_data = json.dumps({'a':'str', 'c': True, 'e': 10, 'b': 11.1, 'd': None, 'f': [1, 2, 3], 'g':(4, 5, 6)})

print(json_data)
# {"a": "str", "c": true, "e": 10, "b": 11.1, "d": null, "f": [1, 2, 3], "g": [4, 5, 6]}

# 反序列化
data = json.loads('{"a": "str", "c": true, "b": 11.1, "e": 10, "d": null, "g": [4, 5, 6], "f": [1, 2, 3]}')
print(data)
# {'c': True, 'e': 10, 'a': 'str', 'g': [4, 5, 6], 'd': None, 'f': [1, 2, 3], 'b': 11.1}

# 序列化到文件中
with open('data.json', 'w') as f:
    json.dump({'country': '中国', 'name': 'august', 'age': 22}, f, indent=4)

# 反序列化文件中的内容
with open('data.json', 'r') as f:
    data = json.load(f)
print(data)
# {'country': '中国', 'name': 'august', 'age': 22}


参数说明

sort_keys:表示序列化时是否对dict的key进行排序

indent:表示缩进的意思,它可以使得数据存储的格式变得更加优雅、可读性更强。如果indent是一个非负整数或字符串,则JSON array元素和object成员将会被以相应的缩进级别进行打印输出;如果indent是0或负数或空字符串,则将只会插入换行,不会有缩进。

尽管indent参数可以使得数据存储的格式变得更加优雅、可读性强,但是那是通过添加一些冗余的空白字符进行填充的。当json被用于网络数据通信时,应该尽可能的减少无用的数据传输,这样可以节省带宽并加快数据传输速度。json模块序列化Python对象后得到的json字符中的,号和:号分隔符后默认都会附加一个空白字符,我们可以通过separators参数重新指定分隔符,从而去除无用的空白字符;

separators:该参数的值应该是一个tuple(item_separator, key_separator)。如果indent是None,其默认值为(', ', ': ');如果indent不为None,则默认值为(',', ': ')。我们可以通过为separator赋值为(',', ':')来消除空白字符

skipkeys:默认值是False,如果dict的keys内的数据不是Python的基本类型,为False时,就会报TypeError的错误;如为True,则会跳过这类key

ensure_ascii: 默认值是True。当它为True的时候,所有非ASCII码字符显示为\uXXXX序列,只需在dump时将ensure_ascii设置为False即可,此时存入json的中文即可正常显示。


JSON与Python之间数据类型对应关系

Python转JSON

Python JSON
dict object
list,tuple array
str string
Int,float numbers
True true
False false
None null

JSON转Python

JSON Python
object dict
array list
string str
number(int) int
number(real) float
true True
false False
null None

注意

Python中的tuple,在序列化时会被转换为array;但是反序列化时,array会被转化为list

对于Python内置的数据类型,json模块可以直接进行序列化/反序列化处理;对于自定义类的对象进行序列化和反序列化时,需要我们自己定义一个方法来完成object和dict之间进行转化。


自定义数据类型的序列化/反序列化

Python是面向对象的编程语言,我们可以自定义需要的数据类型;实际工作中,我们常常会用到自定义数据类型的序列化和反序列化操作。要实现自定义数据类型的序列化和反序列化有两种方式:

  1. 通过转换函数实现
  2. 通过继承JSONEncoder和JSONDecoder类实现

首先先定义一个数据类型

class Student:
    def __init__(self, name, age, sno):
        self.name = name
        self.age = age
        self.sno = sno

    def repr(self):
        return f'Student [name: {self.name}, age: {self.age}, sno: {self.sno}]'


继承JSONEncoder和JSONDecoder实现子类
# encoding:序列化
class StudentJSONEncoder(json.JSONEncoder):
    def default(self, obj: Student) -> dict:
        return {'name': obj.name, 'age': obj.age, 'sno': obj.sno}

# decoding:反序列化    
class StudentJSONDecoder(json.JSONDecoder):
    def decode(self, s: str) :
        dict_data = json.loads(s)
        return Student(**dict_data)

序列化测试

stu = Student('august', 22, 1)

# 方式一:直接调用子类MyJSONEncoder的encode()方法进行序列化
stu_json1 = StudentJSONEncoder().encode(stu)
print(stu_json1)
# {"name": "august", "age": 22, "sno": 1}

# 方式二:将子类MyJSONEncoder作为cls参数的值传递给json.dumps()函数
stu_json2 = json.dumps(stu, cls=StudentJSONEncoder)
print(stu_json2)
# {"name": "august", "age": 22, "sno": 1}

反序列化测试

# 方式一:直接调用子类MyJSONDecoder的decode()方法进行反序列化
stu_data1 = StudentJSONDecoder().decode(stu_json)
print(stu_data1)
# <__main__.Student object at 0x7ff4d4961b50>

# 方式二:将子类MyJSONDecoder作为cls参数的值传递给json.loads()函数
stu_data2 = json.loads(stu_json, cls=StudentJSONDecoder)
print(stu_data2)
# <__main__.Student object at 0x7ff4d4961ad0>


pickle模块

pickle模块实现了用于对Python对象结构进行序列化和反序列化的二进制协议,与json模块不同的是pickle模块序列化和反序列化的过程分别叫做picklingunpickling

  • pickling: 是将Python对象转换为字节流的过程;
  • unpickling:是将字节流二进制文件或字符对象转换回Python对象的过程

pickle模块提供的几个序列化/反序列化的函数与json模块基本一致:

# 将指定的Python对象通过pickle序列化作为bytes对象返回,而不是将其写入文件
dumps(obj, protocol=None, *, fix_imports=True)

# 将通过pickle序列化后得到的字节对象进行反序列化,转换为Python对象并返回
loads(bytes_object, *, fix_imports=True, encoding="ASCII", errors="strict")

# 将指定的Python对象通过pickle序列化后写入打开的文件对象中
dump(obj, file, protocol=None, *, fix_imports=True)

# 从打开的文件对象中读取pickled对象表现形式并返回通过pickle反序列化后得到的Python对象
load(file, *, fix_imports=True, encoding="ASCII", errors="strict")


实例

import pickle

data = [{'a': 'A', 'b': 2, 'c': 3.0}]

# 序列化
data_string = pickle.dumps(data)
print(data_string)
'''
b'\x80\x03]q\x00}q\x01(X\x01\x00\x00\x00aq\x02X\x01\x00\x00\x00Aq
\x03X\x01\x00\x00\x00bq\x04K\x02X\x01\x00\x00\x00cq\x05G@\x08\x00
\x00\x00\x00\x00\x00ua.'
'''

# 反序列化
data_python = pickle.loads(data_string)
print(data_python)
# [{'a': 'A', 'b': 2, 'c': 3.0}]

# 持久化到文件
with open('pickle.txt', 'wb') as f
    pickle.dump(data, f)

# 从文件中读取数据
with open('pickle.txt', 'rb') as f
    print(pickle.load(f))


自定义数据类型的序列化/反序列化

stu = Student('august', 22, 2)
print(f"Data: {stu}")
# 序列化
stu_bytes = pickle.dumps(stu)

print(f'pickle data: {stu_bytes}')

# 反序列化
stu_ = pickle.loads(stu_bytes)
print(stu_)

# 持久化到文件
with open('pickle.txt', 'wb') as f:
    pickle.dump(stu, f)

# 从文件读取数据
with open('pickle.txt', 'rb') as f:
    stu_load = pickle.load(f)
print(stu_load)


shelve模块

shelve是一个简单的数据存储方案,类似key-value数据库,可以很方便的保存Python对象。其内部是通过pickle协议来实现数据序列化。

shelve只有一个open()函数,这个函数用于打开指定的文件(一个持久的字典),然后返回一个shelf对象。shelf是一种持久的、类似字典的对象。

open(filename, flag='c', protocol=None, writeback=False)


参数说明

flag参数表示打开数据存储文件的格式

描述
'r' 以只读模式打开一个已经存在的数据存储文件
'w' 以读写模式打开一个已经存在的数据存储文件
'c' 以读写模式打开一个数据存储文件,如果不存在则创建
'n' 总是创建一个新的、空数据存储文件,并以读写模式打开

protocol参数表示序列化数据所使用的协议版本,默认是pickle v3;

writeback参数表示是否开启回写功能。


实例

# 保存数据
with shelve.open('student') as db:
    db['name'] = 'august'
    db['age'] = 22
    db['hobby'] = ['basketball', 'watch movie', 'play game']
    db['other_info'] = {'sno': 1, 'addr': 'China'}

# 读取数据
with shelve.open('student') as db:
    for key, vlaue in db.items():
        print(key, ": ", value)


自定义数据类型操作

# 保存数据
tom = Student('Tom', 19, 1)
jerry = Student('Jerry', 17, 2)

with shelve.open("stu.db") as db:
    db['Tom'] = tom
    db['Jerry'] = jerry

# 读取数据
with shelve.open("stu.db") as db:
    print(db['Tom'])
    print(db['Jerry'])


总结


对比

json模块常用于编写web接口,将Python数据转换为通用的json格式传递给其他系统或客户端;也可以将Python数据保存到本地文件中,缺点就是明文保存,保密性差。另外,如果需要保存自定义数据类型需要编写额外的转换函数或类。

pickle模块和shelve模块由于使用其他特有的序列化协议,其序列化之后的数据只能被Python识别,因此只能用于Python系统内部。这两个模块相对于json模块的优点在于对于自定义数据类型可以直接序列化和反序列化,不需要额外编写转换函数或类。缺点就是Python2.x和python3.x的默认序列化协议不同,如要互相兼容需在序列化时通过protocol参数指定协议版本。

shelve模块可以看做是pickle模块的升级版,因为shelve使用的就是pickle的序列化协议,但是shelve比pickle提供的操作方式更加简单、方便。shelve模块相对于其他两个模块在Python数据持久化到本地磁盘时有一个很明显的优点就是,它允许我们可以像操作dict一样操作被序列化的数据,而不必一次性的保存或读取所有数据。


建议

  • 需要与外部系统交互时用json模块;

  • 需要将少量、简单Python数据持久化到本地磁盘文件时可以考虑用pickle模块;

  • 需要将大量Python数据持久化到本地磁盘文件或需要一些简单的类似数据库的增删改查功能时,可以考虑用shelve模块。



  • 标签云

  • 支付宝扫码支持一下

  • 微信扫码支持一下



基于Nginx+Supervisord+uWSGI+Django1.11.1+Python3.6.5构建

版权所有 © 2020-2021 August Rush

京ICP备20007446号-1 & 豫公网安备 41100202000460号

网站地图 & RSS | Feed