zoukankan      html  css  js  c++  java
  • 一文了解Python常见的序列化操作

    关于我
    一个有思想的程序猿,终身学习实践者,目前在一个创业团队任team lead,技术栈涉及Android、Python、Java和Go,这个也是我们团队的主要技术栈。
    Github:https://github.com/hylinux1024
    微信公众号:终身开发者(angrycode)

    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文件读写的支持。这也是marshalPython版本不兼容的原因。开发者如果要使用序列化/反序列化,那么应该使用pickle模块。

    常见的方法

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

    序列化一个对象到文件中

    marshal.dumps(value[, version])
    

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

    marshal.load(file)
    

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

    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

    常见的方法

    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(""fooar"))
    ""fooar"
    >>> 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"')
    '"foox08ar'
    >>> 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 学习资料

  • 相关阅读:
    unix/linux中图形界面那些事
    vmware虚拟机工具vmware tools介绍及安装排错
    debian软件源source.list文件格式说明
    Python 基础学习 总结篇
    Git 学习(八)其他
    Git 学习(七)标签管理
    Git 学习(六)分支管理
    Git 学习(五)远程仓库
    Git 学习(四)操作修改和版本穿梭
    页面元素定位 XPath 简介
  • 原文地址:https://www.cnblogs.com/angrycode/p/11416092.html
Copyright © 2011-2022 走看看