一文了解Python常見的序列化操做

關於我
編程界的一名小小程序猿,目前在一個創業團隊任team lead,技術棧涉及Android、Python、Java和Go,這個也是咱們團隊的主要技術棧。 聯繫:hylinux1024@gmail.comhtml

0x00 marshal

marshal使用的是與Python語言相關但與機器無關的二進制來讀寫Python對象的。這種二進制的格式也跟Python語言的版本相關,marshal序列化的格式對不一樣的版本的Python是不兼容的。python

marshal通常用於Python內部對象的序列化。linux

通常地包括:shell

  • 基本類型 booleans, integers,floating point numbers,complex numbers
  • 序列集合類型 strings, bytes, bytearray, tuple, list, set, frozenset, dictionary
  • code對象 code object
  • 其它類型 None, Ellipsis, StopIteration

marshal的主要做用是對Python「編譯」的.pyc文件讀寫的支持。這也是marshalPython版本不兼容的緣由。開發者若是要使用序列化/反序列化,那麼應該使用pickle模塊。編程

常見的方法

marshal.dump(value, file[, version])
複製代碼

序列化一個對象到文件中json

marshal.dumps(value[, version])
複製代碼

序列化一個對象並返回一個bytes對象小程序

marshal.load(file)
複製代碼

從文件中反序列化一個對象數組

marshal.loads(bytes)
複製代碼

bytes二進制數據中反序列化一個對象bash

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

常見的方法

pickle.dump(obj, file, protocol=None, *, fix_imports=True)
複製代碼

obj對象序列化到一個file文件中,該方法與Pickler(file, protocol).dump(obj)等價。

pickle.dumps(obj, protocol=None, *, fix_imports=True)
複製代碼

obj對象序列化成bytes二進制數據。

pickle.load(file, *, fix_imports=True, encoding="ASCII", errors="strict")
複製代碼

file文件中反序列化一個對象,該方法與Unpickler(file).load()等價。

pickle.loads(bytes_object, *, fix_imports=True, encoding="ASCII", errors="strict")
複製代碼

從二進制數據bytes_object反序列化對象。

序列化例子

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
複製代碼

反序列化例子

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它與marshalpickle同樣擁有類似的API

常見的方法

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文件中

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對象

json.load(fp, *, cls=None, object_hook=None, parse_float=None, parse_int=None, parse_constant=None, object_pairs_hook=None, **kw)
複製代碼

從文件中反序列化成一個對象

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格式文檔中反序列化成一個對象

jsonPython對象的轉化對照表

JSON Python
object dict
list,tuple array
str string
int, float, int- & float-derived Enums number
True true
False false
None null

對於基本類型、序列、以及包含基本類型的集合類型json均可以很好的完成序列化工做。

序列化例子

>>> 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"]'
複製代碼

反序列化例子

>>> 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

{
  "__complex__": true,
  "real": 42,
  "imaginary": 36
}
複製代碼

要把這個json文檔反序列化成Python對象,就須要定義轉化的方法

# 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

# 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

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

>>> 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來定義轉化的邏輯。

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了。

# 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常見的序列化工具備marshalpicklejsonmarshal主要用於Python.pyc文件,並與Python版本相關。它不能序列化用戶定義的類。 picklePython對象的序列化工具則比marshal更通用些,它能夠兼容Python的不一樣版本。json是一種語言無關的數據結構,普遍用於各類網絡應用尤爲在REST API的服務中的數據交互。

0x04 學習資料

相關文章
相關標籤/搜索