一篇文章了解Python中常见的序列化操作

时间:2022-12-02 10:22:55

0x00 marshal

marshal使用的是与python语言相关但与机器无关的二进制来读写python对象的。这种二进制的格式也跟python语言的版本相关,marshal序列化的格式对不同的版本的python是不兼容的。

marshal一般用于python内部对象的序列化。

一般地包括:

  • 基本类型 booleans, integers,floating point numbers,complex numbers
  • 序列集合类型 strings, bytes, bytearray, tuple, list, set, frozenset, dictionary
  • code对象 code object
  • 其它类型 none, ellipsis, stopiteration

marshal的主要作用是对python“编译”的.pyc文件读写的支持。这也是marshal对python版本不兼容的原因。开发者如果要使用序列化/反序列化,那么应该使用pickle模块。

常见的方法

?
1
marshal.dump(value, file[, version])

序列化一个对象到文件中

?
1
marshal.dumps(value[, version])

序列化一个对象并返回一个bytes对象

?
1
marshal.load(file)

从文件中反序列化一个对象

?
1
marshal.loads(bytes)

从bytes二进制数据中反序列化一个对象

0x01 pickle

pickle模块也能够以二进制的方式对python对象进行读写。相比marshal提供基本的序列化能力,pickle的序列化应用更加广泛。

pickle序列化后的数据也是与python语言相关的,即其它语言例如java无法读取由python通过pickle序列化的二进制数据。如果要使用与语言无法的序列化那么我们应该使用json。下文将会说明。

能被pickle序列化的数据类型有:

  • none, true, and false
  • integers, floating point numbers, complex numbers
  • strings, bytes, bytearrays
  • tuples, lists, sets, and dictionaries 以及包含可以被pickle序列化对象
  • 在模块顶层定义的函数对象 (使用 def定义的, 而不是 lambda表达式)
  • 在模块顶层定义内置函数
  • 在模式顶层定义的类
  • 一个类的__dict__包含了可序列化的对象或__getstate__()方法返回了能够被序列化的对象

如果pickle一个不支持序列化的对象时将会抛出picklingerror。

常见的方法

?
1
pickle.dump(obj, file, protocol=none, *, fix_imports=true)

将obj对象序列化到一个file文件中,该方法与pickler(file, protocol).dump(obj)等价。

?
1
pickle.dumps(obj, protocol=none, *, fix_imports=true)

将obj对象序列化成bytes二进制数据。

?
1
pickle.load(file, *, fix_imports=true, encoding="ascii", errors="strict")

从file文件中反序列化一个对象,该方法与unpickler(file).load()等价。

?
1
pickle.loads(bytes_object, *, fix_imports=true, encoding="ascii", errors="strict")

从二进制数据bytes_object反序列化对象。

序列化例子

?
1
2
3
4
5
6
7
8
9
10
11
12
13
import pickle
 
# 定义了一个包含了可以被序列化对象的字典
data = {
 'a': [1, 2.0, 3, 4 + 6j],
 'b': ("character string", b"byte string"),
 'c': {none, true, false}
}
 
with open('data.pickle', 'wb') as f:
 # 序列化对象到一个data.pickle文件中
 # 指定了序列化格式的版本pickle.highest_protocol
 pickle.dump(data, f, pickle.highest_protocol)

执行之后在文件夹中多一个data.pickle文件

serialization
├── data.pickle
├── pickles.py
└── unpickles.py

反序列化例子

?
1
2
3
4
5
6
7
8
9
10
11
12
import pickle
 
with open('data.pickle', 'rb') as f:
 # 从data.pickle文件中反序列化对象
 # pickle能够自动检测序列化文件的版本
 # 所以这里可以不用版本号
 data = pickle.load(f)
 
 print(data)
 
# 执行后结果
# {'a': [1, 2.0, 3, (4+6j)], 'b': ('character string', b'byte string'), 'c': {false, true, none}}

0x02 json
json是与语言无关,非常通用的数据交互格式。在python它与marshal和pickle一样拥有相似的api。

常见的方法

?
1
json.dump(obj, fp, *, skipkeys=false, ensure_ascii=true, check_circular=true, allow_nan=true, cls=none, indent=none, separators=none, default=none, sort_keys=false, **kw)

序列化对象到fp文件中

?
1
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)

将obj序列化成json对象

?
1
json.load(fp, *, cls=none, object_hook=none, parse_float=none, parse_int=none, parse_constant=none, object_pairs_hook=none, **kw)

从文件中反序列化成一个对象

?
1
json.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与python对象的转化对照表

json python
object dict
list,tuple array
str string
int, float, int- & float-derived enums number
true true
false false
none null

对于基本类型、序列、以及包含基本类型的集合类型json都可以很好的完成序列化工作。

序列化例子

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> import json
>>> json.dumps(['foo', {'bar': ('baz', none, 1.0, 2)}])
'["foo", {"bar": ["baz", null, 1.0, 2]}]'
>>> print(json.dumps("\"foo\bar"))
"\"foo\bar"
>>> print(json.dumps('\u1234'))
"\u1234"
>>> print(json.dumps('\\'))
"\\"
>>> print(json.dumps({"c": 0, "b": 0, "a": 0}, sort_keys=true))
{"a": 0, "b": 0, "c": 0}
>>> from io import stringio
>>> io = stringio()
>>> json.dump(['streaming api'], io)
>>> io.getvalue()
'["streaming api"]'

反序列化例子

?
1
2
3
4
5
6
7
8
9
>>> import json
>>> json.loads('["foo", {"bar":["baz", null, 1.0, 2]}]')
['foo', {'bar': ['baz', none, 1.0, 2]}]
>>> json.loads('"\\"foo\\bar"')
'"foo\x08ar'
>>> from io import stringio
>>> io = stringio('["streaming api"]')
>>> json.load(io)
['streaming api']

对于object的情况就复杂一些了

例如定义了复数complex对象的json文档

complex_data.json

?
1
2
3
4
5
{
 "__complex__": true,
 "real": 42,
 "imaginary": 36
}

要把这个json文档反序列化成python对象,就需要定义转化的方法

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# coding=utf-8
import json
 
# 定义转化函数,将json中的内容转化成complex对象
def decode_complex(dct):
 if "__complex__" in dct:
  return complex(dct["real"], dct["imaginary"])
 else:
  return dct
 
if __name__ == '__main__':
 with open("complex_data.json") as complex_data:
  # object_hook指定转化的函数
  z = json.load(complex_data, object_hook=decode_complex)
  print(type(z))
  print(z)
 
# 执行结果
# <class 'complex'>
# (42+36j)

如果不指定object_hook,那么默认将json文档中的object转成dict

?
1
2
3
4
5
6
7
8
9
10
11
12
13
# coding=utf-8
import json
 
if __name__ == '__main__':
 
 with open("complex_data.json") as complex_data:
  # 这里不指定object_hook
  z2 = json.loads(complex_data.read())
  print(type(z2))
  print(z2)
# 执行结果
# <class 'dict'>
# {'__complex__': true, 'real': 42, 'imaginary': 36}

可以看到json文档中的object转成了dict对象。

一般情况下这样使用似乎也没什么问题,但如果对类型要求很高的场景就需要明确定义转化的方法了。

除了object_hook参数还可以使用json.jsonencoder

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import json
 
class complexencoder(json.jsonencoder):
 def default(self, obj):
  if isinstance(obj, complex):
   # 如果complex对象这里转成数组的形式
   return [obj.real, obj.imag]
   # 默认处理
  return json.jsonencoder.default(self, obj)
 
if __name__ == '__main__':
 c = json.dumps(2 + 1j, cls=complexencoder)
 print(type(c))
 print(c)
 
# 执行结果
# <class 'str'>
# [2.0, 1.0]

因为json模块并不是对所有类型都能够自动完成序列化的,对于不支持的类型,会直接抛出typeerror。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
>>> import datetime
>>> d = datetime.datetime.now()
>>> dct = {'birthday':d,'uid':124,'name':'jack'}
>>> dct
{'birthday': datetime.datetime(2019, 6, 14, 11, 16, 17, 434361), 'uid': 124, 'name': 'jack'}
>>> json.dumps(dct)
traceback (most recent call last):
 file "<pyshell#19>", line 1, in <module>
 json.dumps(dct)
 file "/library/frameworks/python.framework/versions/3.7/lib/python3.7/json/__init__.py", line 231, in dumps
 return _default_encoder.encode(obj)
 file "/library/frameworks/python.framework/versions/3.7/lib/python3.7/json/encoder.py", line 199, in encode
 chunks = self.iterencode(o, _one_shot=true)
 file "/library/frameworks/python.framework/versions/3.7/lib/python3.7/json/encoder.py", line 257, in iterencode
 return _iterencode(o, 0)
 file "/library/frameworks/python.framework/versions/3.7/lib/python3.7/json/encoder.py", line 179, in default
 raise typeerror(f'object of type {o.__class__.__name__} '
typeerror: object of type datetime is not json serializable

对于不支持序列化的类型例如datetime以及自定义类型,就需要使用jsonencoder来定义转化的逻辑。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import json
import datetime
 
# 定义日期类型的jsonencoder
class datetimeencoder(json.jsonencoder):
 
 def default(self, obj):
  if isinstance(obj, datetime.datetime):
   return obj.strftime('%y-%m-%d %h:%m:%s')
  elif isinstance(obj, datetime.date):
   return obj.strftime('%y-%m-%d')
  else:
   return json.jsonencoder.default(self, obj)
 
if __name__ == '__main__':
 d = datetime.date.today()
 dct = {"birthday": d, "name": "jack"}
 data = json.dumps(dct, cls=datetimeencoder)
 print(data)
 
# 执行结果
# {"birthday": "2019-06-14", "name": "jack"}

现在我们希望发序列化时,能够将json文档中的日期格式转化成datetime.date对象,这时就需要使用到json.jsondecoder了。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# coding=utf-8
import json
import datetime
 
# 定义decoder解析json
class datetimedecoder(json.jsondecoder):
 
 # 构造方法
 def __init__(self):
  super().__init__(object_hook=self.dict2obj)
 
 def dict2obj(self, d):
  if isinstance(d, dict):
   for k in d:
    if isinstance(d[k], str):
     # 对日期格式进行解析,生成一个date对象
     dat = d[k].split("-")
     if len(dat) == 3:
      date = datetime.date(int(dat[0]), int(dat[1]), int(dat[2]))
      d[k] = date
  return d
 
if __name__ == '__main__':
 d = datetime.date.today()
 dct = {"birthday": d, "name": "jack"}
 data = json.dumps(dct, cls=datetimeencoder)
 # print(data)
 
 obj = json.loads(data, cls=datetimedecoder)
 print(type(obj))
 print(obj)
 
# 执行结果
# {"birthday": "2019-06-14", "name": "jack"}
# <class 'dict'>
# {'birthday': datetime.date(2019, 6, 14), 'name': 'jack'}

0x03 总结一下

python常见的序列化工具有marshal、pickle和json。marshal主要用于python的.pyc文件,并与python版本相关。它不能序列化用户定义的类。

pickle是python对象的序列化工具则比marshal更通用些,它可以兼容python的不同版本。json是一种语言无关的数据结构,广泛用于各种网络应用尤其在rest api的服务中的数据交互。

0x04 学习资料

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对服务器之家的支持。

原文链接:https://juejin.im/post/5d0362755188255e780b6815