Python对象的序列化和反序列化:第1部分

Python对象序列化和反序列化是任何非平凡程序的重要方面。 如果在Python中将某些内容保存到文件,读取配置文件或响应HTTP请求,则进行对象序列化和反序列化。

从某种意义上说,序列化和反序列化是世界上最无聊的事情。 谁在乎所有格式和协议? 您只想保留或流传输一些Python对象,然后将它们恢复原样。

这是从概念层面看世界的非常健康的方式。 但是,在务实的级别上,选择哪种序列化方案,格式或协议可能会确定程序运行的速度,程序的安全性,维护状态的自由度以及与其他程序的互操作性。系统。

之所以选择太多,是因为不同的情况需要不同的解决方案。 没有“一刀切”的商品。 在这个分为两部分的教程中,我将介绍最成功的序列化和反序列化方案的优缺点,展示如何使用它们,并提供面对特定用例时在它们之间进行选择的指导。

运行示例

在以下各节中,我将使用不同的序列化器序列化和反序列化相同的Python对象图。 为避免重复,我将在此处定义这些对象图。

简单对象图

简单对象图是一个字典,其中包含一个整数列表,一个字符串,一个浮点数,一个布尔值和一个None。

simple = dict(int_list=[1, 2, 3],

              text='string',

              number=3.44,

              boolean=True,

              none=None)

复杂对象图

复杂对象图也是一个字典,但是它包含datetime对象和具有self.simple属性的用户定义的类实例,该属性设置为简单对象图。

from datetime import datetime



class A(object):

    def __init__(self, simple):

        self.simple = simple        

    def __eq__(self, other):

        if not hasattr(other, 'simple'):

            return False

        return self.simple == other.simple

    def __ne__(self, other):

        if not hasattr(other, 'simple'):

            return True

        return self.simple != other.simple



complex = dict(a=A(simple), when=datetime(2016, 3, 7))

泡菜

泡菜是主食。 这是本机Python对象序列化格式。 pickle接口提供了四种方法:dump,dumps,load和load。 dump()方法序列化为打开的文件(类似文件的对象)。 dumps()方法序列化为字符串。 load()方法从打开的类似文件的对象中反序列化。 loads()方法从字符串反序列化。

Pickle默认情况下支持文本协议,但也具有二进制协议,该协议效率更高,但不是人类可读的(调试时很有用)。

这是使用两种协议将Python对象图腌制为字符串和文件的方法。

import cPickle as pickle



pickle.dumps(simple)

"(dp1\nS'text'\np2\nS'string'\np3\nsS'none'\np4\nNsS'boolean'\np5\nI01\nsS'number'\np6\nF3.4399999999999999\nsS'int_list'\np7\n(lp8\nI1\naI2\naI3\nas."



pickle.dumps(simple, protocol=pickle.HIGHEST_PROTOCOL)

'\x80\x02}q\x01(U\x04textq\x02U\x06stringq\x03U\x04noneq\x04NU\x07boolean\x88U\x06numberq\x05G@\x0b\x85\x1e\xb8Q\xeb\x85U\x08int_list]q\x06(K\x01K\x02K\x03eu.'

二进制表示形式可能看起来更大,但是由于其表示形式,这是一种错觉。 转储到文件时,文本协议为130字节,而二进制协议仅为85字节。

pickle.dump(simple, open('simple1.pkl', 'w'))

pickle.dump(simple, open('simple2.pkl', 'wb'), protocol=pickle.HIGHEST_PROTOCOL)



ls -la sim*.*

-rw-r--r--  1 gigi  staff  130 Mar  9 02:42 simple1.pkl

-rw-r--r--  1 gigi  staff   85 Mar  9 02:43 simple2.pkl

从字符串中解开是很简单的:

x = pickle.loads("(dp1\nS'text'\np2\nS'string'\np3\nsS'none'\np4\nNsS'boolean'\np5\nI01\nsS'number'\np6\nF3.4399999999999999\nsS'int_list'\np7\n(lp8\nI1\naI2\naI3\nas.")

assert x == simple



x = pickle.loads('\x80\x02}q\x01(U\x04textq\x02U\x06stringq\x03U\x04noneq\x04NU\x07boolean\x88U\x06numberq\x05G@\x0b\x85\x1e\xb8Q\xeb\x85U\x08int_list]q\x06(K\x01K\x02K\x03eu.')

assert x == simple

请注意,pickle可以自动找出协议。 甚至无需为二进制协议指定协议。

从文件中取消选取也很容易。 您只需要提供一个打开的文件。

x = pickle.load(open('simple1.pkl'))

assert x == simple



x = pickle.load(open('simple2.pkl'))

assert x == simple



x = pickle.load(open('simple2.pkl', 'rb'))

assert x == simple

根据文档,您应该使用“ rb”模式打开二进制泡菜,但是正如您所见,它可以以任何一种方式工作。

让我们看看pickle如何处理复杂的对象图。

pickle.dumps(complex)

"(dp1\nS'a'\nccopy_reg\n_reconstructor\np2\n(c__main__\nA\np3\nc__builtin__\nobject\np4\nNtRp5\n(dp6\nS'simple'\np7\n(dp8\nS'text'\np9\nS'string'\np10\nsS'none'\np11\nNsS'boolean'\np12\nI01\nsS'number'\np13\nF3.4399999999999999\nsS'int_list'\np14\n(lp15\nI1\naI2\naI3\nassbsS'when'\np16\ncdatetime\ndatetime\np17\n(S'\\x07\\xe0\\x03\\x07\\x00\\x00\\x00\\x00\\x00\\x00'\ntRp18\ns."



pickle.dumps(complex, protocol=pickle.HIGHEST_PROTOCOL)

'\x80\x02}q\x01(U\x01ac__main__\nA\nq\x02)\x81q\x03}q\x04U\x06simpleq\x05}q\x06(U\x04textq\x07U\x06stringq\x08U\x04noneq\tNU\x07boolean\x88U\x06numberq\nG@\x0b\x85\x1e\xb8Q\xeb\x85U\x08int_list]q\x0b(K\x01K\x02K\x03eusbU\x04whenq\x0ccdatetime\ndatetime\nq\rU\n\x07\xe0\x03\x07\x00\x00\x00\x00\x00\x00\x85Rq\x0eu.'



pickle.dump(complex, open('complex1.pkl', 'w'))

pickle.dump(complex, open('complex2.pkl', 'wb'), protocol=pickle.HIGHEST_PROTOCOL)



ls -la comp*.*

-rw-r--r--  1 gigi  staff  327 Mar  9 02:58 complex1.pkl

-rw-r--r--  1 gigi  staff  171 Mar  9 02:58 complex2.pkl

对于复杂的对象图,二进制协议的效率更高。

JSON格式

从Python 2.5开始,JSON(JavaScript对象符号)已成为Python标准库的一部分。 现在,我将其视为本机格式。 它是基于文本的格式,就对象序列化而言,它是非官方的网络之王。 它的类型系统自然对JavaScript进行建模,因此非常有限。

让我们序列化和反序列化简单和复杂的对象图,看看会发生什么。 该界面几乎与pickle界面相同。 您具有dump()dumps()load()loads()函数。 但是,没有可供选择的协议,并且有许多可选的参数来控制过程。 让我们通过转储不带任何特殊参数的简单对象图来开始简单工作:

import json

print json.dumps(simple)

{"text": "string", "none": null, "boolean": true, "number": 3.44, "int_list": [1, 2, 3]}

输出看起来很可读,但是没有缩进。 对于较大的对象图,这可能是一个问题。 让我们缩进输出:

print json.dumps(simple, indent=4)

{

    "text": "string",

    "none": null,

    "boolean": true,

    "number": 3.44,

    "int_list": [

        1,

        2,

        3

    ]

}

看起来好多了。 让我们继续复杂对象图。

json.dumps(complex)

---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-19-1be2d89d5d0d> in <module>()

----> 1 json.dumps(complex)



/usr/local/Cellar/python/2.7.10/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/__init__.pyc in dumps(obj, skipkeys, ensure_ascii, check_circular, allow_nan, cls, indent, separators, encoding, default, sort_keys, **kw)

    241         cls is None and indent is None and separators is None and

    242         encoding == 'utf-8' and default is None and not sort_keys and not kw):

--> 243         return _default_encoder.encode(obj)

    244     if cls is None:

    245         cls = JSONEncoder



/usr/local/Cellar/python/2.7.10/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/encoder.pyc in encode(self, o)

    205         # exceptions aren't as detailed.  The list call should be roughly

    206         # equivalent to the PySequence_Fast that ''.join() would do.

--> 207         chunks = self.iterencode(o, _one_shot=True)

    208         if not isinstance(chunks, (list, tuple)):

    209             chunks = list(chunks)



/usr/local/Cellar/python/2.7.10/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/encoder.pyc in iterencode(self, o, _one_shot)

    268                 self.key_separator, self.item_separator, self.sort_keys,

    269                 self.skipkeys, _one_shot)

--> 270         return _iterencode(o, 0)

    271

    272 def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,



/usr/local/Cellar/python/2.7.10/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/encoder.pyc in default(self, o)

    182

    183         """

--> 184         raise TypeError(repr(o) + " is not JSON serializable")

    185

    186     def encode(self, o):



TypeError: <__main__.A object at 0x10f367cd0> is not JSON serializable

哇! 看起来一点也不好。 发生了什么? 错误消息是A对象不可JSON序列化。 请记住,JSON的类型系统非常有限,它无法自动序列化用户定义的类。 解决该问题的方法是子类化json模块使用的JSONEncoder类,并实现default() ,只要JSON编码器遇到无法序列化的对象,就会调用该default()

自定义编码器的工作是将其转换为JSON编码器能够编码的Python对象图。 在这种情况下,我们有两个需要特殊编码的对象: datetime对象和A类。 以下编码器可以完成这项工作。 每个特殊对象都将转换为dict ,其中键是由下划线(双下划线)包围的类型的名称。 这对于解码很重要。

from datetime import datetime

import json





class CustomEncoder(json.JSONEncoder):

     def default(self, o):

         if isinstance(o, datetime):

             return {'__datetime__': o.replace(microsecond=0).isoformat()}

         return {'__{}__'.format(o.__class__.__name__): o.__dict__}

让我们使用自定义编码器再试一次:

serialized = json.dumps(complex, indent=4, cls=CustomEncoder)

print serialized



{

    "a": {

        "__A__": {

            "simple": {

                "text": "string",

                "none": null,

                "boolean": true,

                "number": 3.44,

                "int_list": [

                    1,

                    2,

                    3

                ]

            }

        }

    },

    "when": {

        "__datetime__": "2016-03-07T00:00:00"

    }

}

这很漂亮。 正确地序列化了复杂对象图,并通过键“ __A__”和“ __datetime__”保留了组件的原始类型信息。 如果您使用dunders作为名称,则需要提出另一种约定来表示特殊类型。

让我们解码复杂的对象图。

> deserialized = json.loads(serialized)

> deserialized == complex

False

嗯,反序列化工作了(没有错误),但是它与我们序列化的原始复杂对象图不同。 出了点问题。 让我们看一下反序列化的对象图。 我将使用pprint模块的pprint功能进行漂亮的打印。

> from pprint import pprint

> pprint(deserialized)

{u'a': {u'__A__': {u'simple': {u'boolean': True,

                               u'int_list': [1, 2, 3],

                               u'none': None,

                               u'number': 3.44,

                               u'text': u'string'}}},

 u'when': {u'__datetime__': u'2016-03-07T00:00:00'}}

好。 问题在于json模块对A类甚至标准的datetime对象一无所知。 默认情况下,它只是将所有内容反序列化为与其类型系统匹配的Python对象。 为了返回丰富的Python对象图,您需要自定义解码。

不需要自定义解码器子类。 load()loads()函数提供“ object_hook”参数,可让您提供一个将字典转换为对象的自定义函数。

def decode_object(o):

    if '__A__' in o:

        a = A()

        a.__dict__.update(o['__A__'])

        return a

    elif '__datetime__' in o:

        return datetime.strptime(o['__datetime__'], '%Y-%m-%dT%H:%M:%S')        

    return o

让我们使用decode_object()函数作为loads() object_hook参数的参数进行解码。

> deserialized = json.loads(serialized, object_hook=decode_object)

> print deserialized

{u'a': <__main__.A object at 0x10d984790>, u'when': datetime.datetime(2016, 3, 7, 0, 0)}



> deserialized == complex

True

结论

在本教程的第一部分中,您了解了Python对象的序列化和反序列化的一般概念,并探索了使用Pickle和JSON序列化Python对象的细节。

在第二部分中,您将了解YAML,性能和安全性问题,以及对其他序列化方案的快速回顾。

无论您是刚刚起步还是想学习新技能的经验丰富的程序员,都可以通过我们完整的python教程指南学习Python。

翻译自: https://code.tutsplus.com/tutorials/serialization-and-deserialization-of-python-objects-part-1--cms-26183

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值