Python中关于对象序列化实现和原理

这篇具有很好参考价值的文章主要介绍了Python中关于对象序列化实现和原理。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

pickle模块可以实现任意的Python对象转换为一系列字节(即序列化对象)的算法。这些字节流可以被传输或存储,接着也可以重构为—个和原先对象具有相同特征的新对象。

注意:

  • pickle的文档清晰的表明它不提供安全保证。实际上,反序列化后可以执行任意代码,所以慎用
  • pickle来作为内部进程通信或者数据存储,也不要相信那些你不能验证安全性的数据。
  • hmac模块,它提供了—个以安全方式验证序列化数据源的示例。

字符串的编码和解码

第一个示例是使用 dumps() 将一个数据结构编码为一个字符串,然后将其输出到控制台。它使用内置类型组成的数据结构,其实任何类的实例都可以被序列化,如后面的例子所示。

import pickle
import pprint

data = [{'a': 'A', 'b': 2, 'c': 3.0}]
print('DATA:', end=' ')
pprint.pprint(data)

data_string = pickle.dumps(data)
print('PICKLE: {!r}'.format(data_string))

默认情况下,Python 3 的序列化以兼容的二进制形式进行。

$ python3 pickle_string.py

DATA: [{'a': 'A', 'b': 2, 'c': 3.0}]
PICKLE: b'\x80\x03]q\x00}q\x01(X\x01\x00\x00\x00cq\x02G@\x08\x00
\x00\x00\x00\x00\x00X\x01\x00\x00\x00bq\x03K\x02X\x01\x00\x00\x0
0aq\x04X\x01\x00\x00\x00Aq\x05ua.'

一旦数据被序列化,你就可以把它写入到文件、socket、管道等等中。之后你可以读取这个文件,反序列化这些数据来构造具有相同值的新对象。

import pickle
import pprint

data1 = [{'a': 'A', 'b': 2, 'c': 3.0}]
print('BEFORE: ', end=' ')
pprint.pprint(data1)

data1_string = pickle.dumps(data1)

data2 = pickle.loads(data1_string)
print('AFTER : ', end=' ')
pprint.pprint(data2)

print('SAME? :', (data1 is data2))
print('EQUAL?:', (data1 == data2))

新对象和之前的对象相等,但不是之前的对象。

$ python3 pickle_unpickle.py

BEFORE:  [{'a': 'A', 'b': 2, 'c': 3.0}]
AFTER :  [{'a': 'A', 'b': 2, 'c': 3.0}]
SAME? : False
EQUAL?: True

流的序列化

pickle 除了提供 dumps() 和 loads() ,还提供了非常方便的函数用于操作文件流。支持同时写多个对象到同一个流中,然后在不知道有多少个对象或不知道它们有多大时,能够从这个流中读取到这些对象。

pickle_stream.py

import io
import pickle
import pprint

class SimpleObject:

    def __init__(self, name):
        self.name = name
        self.name_backwards = name[::-1]
        return

data = []
data.append(SimpleObject('pickle'))
data.append(SimpleObject('preserve'))
data.append(SimpleObject('last'))

# 模拟一个文件
out_s = io.BytesIO()

# 写入流中
for o in data:
    print('WRITING : {} ({})'.format(o.name, o.name_backwards))
    pickle.dump(o, out_s)
    out_s.flush()

# 设置一个可读取的流
in_s = io.BytesIO(out_s.getvalue())

# 读取数据
while True:
    try:
        o = pickle.load(in_s)
    except EOFError:
        break
    else:
        print('READ    : {} ({})'.format(
            o.name, o.name_backwards))

这个例子使用两个 BytesIO 缓冲区来模拟流。一个接收序列化对象,另一个通过 load() 方法读取第一个的值。一个简单的数据库格式也可以使用序列化来存储对象。 shelve 模块就是这样使用的一个范例。

$ python3 pickle_stream.py

WRITING : pickle (elkcip)
WRITING : preserve (evreserp)
WRITING : last (tsal)
READ    : pickle (elkcip)
READ    : preserve (evreserp)
READ    : last (tsal)

除了用于存储数据,序列化在用于内部进程通信时也是非常灵活的。比如,使用 os.fork() 和 os.pipe() ,可以建立一些工作进程,它们从一个管道中读取任务说明并把结果输出到另一个管道。操作这些工作池、发送任务和接受返回的核心代码可以复用,因为任务和返回对象不是一个特殊的类。如果使用管道或者套接字,就不要忘记在序列化每个对象后刷新它们,并通过它们之间的连接将数据推送到另外一端。查看 multiprocessing 模块构建一个可复用的任务池管理器。

重建对象的问题

在处理自定义类时,你应该保证这些被序列化的类会在进程命名空间出现 只有数据实例才能被序列化,而不能是定义的类。在反序列化时,类的名字被用于寻找构造器以便创建新对象。接下来这个例子,是将一个类实例写入到文件中。

pickle_dump_to_file_1.py
import pickle
import sys

class SimpleObject:

    def __init__(self, name):
        self.name = name
        l = list(name)
        l.reverse()
        self.name_backwards = ''.join(l)

if __name__ == '__main__':
    data = []
    data.append(SimpleObject('pickle'))
    data.append(SimpleObject('preserve'))
    data.append(SimpleObject('last'))

    filename = sys.argv[1]

    with open(filename, 'wb') as out_s:
        for o in data:
            print('WRITING: {} ({})'.format(
                o.name, o.name_backwards))
            pickle.dump(o, out_s)

当我运行这个脚本时,它会创建名为我在命令行中输入的参数的文件。

$ python3 pickle_dump_to_file_1.py test.dat

WRITING: pickle (elkcip)
WRITING: preserve (evreserp)
WRITING: last (tsal)

之后尝试将刚才的序列化的结果对象装载进来是失败的。

pickle_load_from_file_1.py

import pickle
import pprint
import sys

filename = sys.argv[1]

with open(filename, 'rb') as in_s:
    while True:
        try:
            o = pickle.load(in_s)
        except EOFError:
            break
        else:
            print('READ: {} ({})'.format(
                o.name, o.name_backwards))

这个版本失败了,因为这里没有可用的 SimpleObject 类。

$ python3 pickle_load_from_file_1.py test.dat

Traceback (most recent call last):
  File "pickle_load_from_file_1.py", line 15, in <module>
    o = pickle.load(in_s)
AttributeError: Can't get attribute 'SimpleObject' on <module '_
_main__' from 'pickle_load_from_file_1.py'>

下面是正确的版本,它从一开始的脚本中导入了 SimpleObject 类。添加导入语句可以让该脚本找到类并构建对象。

from pickle_dump_to_file_1 import SimpleObject

现在运行修改后的脚本可以得到预期的结果了。

$ python3 pickle_load_from_file_2.py test.dat

READ: pickle (elkcip)
READ: preserve (evreserp)
READ: last (tsal)

无法序列化的对象

不是所有对象都可以被序列化的。如套接字、文件句柄、数据库连接或其他具有运行时状态的对象,可能依赖于操作系统或其他进程无法有效的存储下来。那些不能被序列化的类可以定义_getstate___setstate__()方法来返回实例在被序列化时的状态。

_getstate_()方法须返回一个包含该对象内部状态的对象。一种便捷的方式是使用字典,字典的值可以是任意可序列化对象。然后状态会被存储,当对象序列化时传递给__setstate__方法。

pickle_state.py

import pickle

class State:

    def __init__(self, name):
        self.name = name

    def __repr__(self):
        return 'State({!r})'.format(self.__dict__)

class MyClass:

    def __init__(self, name):
        print('MyClass.__init__({})'.format(name))
        self._set_name(name)

    def _set_name(self, name):
        self.name = name
        self.computed = name[::-1]

    def __repr__(self):
        return 'MyClass({!r}) (computed={!r})'.format(
            self.name, self.computed)

    def __getstate__(self):
        state = State(self.name)
        print('__getstate__ -> {!r}'.format(state))
        return state

    def __setstate__(self, state):
        print('__setstate__({!r})'.format(state))
        self._set_name(state.name)

inst = MyClass('name here')
print('Before:', inst)

dumped = pickle.dumps(inst)

reloaded = pickle.loads(dumped)
print('After:', reloaded)

这个例子使用一个单独的 State对象存储MyClass的内部状态。当 MyClass的实例反序列化时,会给_setstate_()传入一个 state的实例去初始化新的对象。

如果__getstate__()返回值是false,则__setstate__()在对象反序列化时不会被调用。

循环引用

序列化协议会自动处理对象间的循环引用,所以即使复杂的数据结构也不需要去特殊处理。考虑下图,它包含了多个循环,但正确的结构仍然能被反序列化输出。

序列化一个循环引用的数据结构

pickle_cycle.py

import pickle

class Node:
    """一个简单的有向图
    """
    def __init__(self, name):
        self.name = name
        self.connections = []

    def add_edge(self, node):
         """在这个节点和其他节点间建立一条边
                 """
        self.connections.append(node)

    def __iter__(self):
        return iter(self.connections)

def preorder_traversal(root, seen=None, parent=None):
    """给一个图生成边的生成器函数
    """
    if seen is None:
        seen = set()
    yield (parent, root)
    if root in seen:
        return
    seen.add(root)
    for node in root:
        recurse = preorder_traversal(node, seen, root)
        for parent, subnode in recurse:
            yield (parent, subnode)

def show_edges(root):
     """打印输出图的所有边
         """
    for parent, child in preorder_traversal(root):
        if not parent:
            continue
        print('{:>5} -> {:>2} ({})'.format(
            parent.name, child.name, id(child)))

# 创建有向图
root = Node('root')
a = Node('a')
b = Node('b')
c = Node('c')

# 给节点间添加边
root.add_edge(a)
root.add_edge(b)
a.add_edge(b)
b.add_edge(a)
b.add_edge(c)
a.add_edge(a)

print('ORIGINAL GRAPH:')
show_edges(root)
#学习中遇到问题没人解答?小编创建了一个Python学习交流群:711312441
# 序列化和反序列化有向图
# 产生一组新的节点
dumped = pickle.dumps(root)
reloaded = pickle.loads(dumped)

print('\nRELOADED GRAPH:')
show_edges(reloaded)

经过序列化和反序列化,这些新的有向图节点对象并不是一开始创建的那些对象,但对象之间的关系保持不变,这可以通过检查对象 id() 返回的值验证。文章来源地址https://www.toymoban.com/news/detail-777020.html

$ python3 pickle_cycle.py

ORIGINAL GRAPH:
 root ->  a (4315798272)
    a ->  b (4315798384)
    b ->  a (4315798272)
    b ->  c (4315799112)
    a ->  a (4315798272)
 root ->  b (4315798384)

RELOADED GRAPH:
 root ->  a (4315904096)
    a ->  b (4315904152)
    b ->  a (4315904096)
    b ->  c (4315904208)
    a ->  a (4315904096)
 root ->  b (4315904152

到了这里,关于Python中关于对象序列化实现和原理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • springboot对象序列化自定义序列化注解

    在开发中有时候会遇到一些内容返回时需要翻译,或者一些内容在序列化之前需要特殊处理(脱敏啥的)。 一般对单个属性可以直接用 jackson 的序列化注解对某个属性单独处理 com.fasterxml.jackson.databind.annotation.JsonSerialize(using= xxx.class) 但是直接使用不太灵活,可以进一步引入注

    2024年02月07日
    浏览(16)
  • 4.4. 对象序列化与反序列化

    4.4. 对象序列化与反序列化

    在本节中,我们将详细讨论Java中的对象序列化与反序列化概念、使用方法以及实例。对象序列化是将对象的状态信息转换为字节流的过程,而反序列化则相反,是将字节流恢复为对象的过程。 4.4.1 为什么需要对象序列化? 对象序列化的主要目的是为了在不同的系统间传输对

    2024年02月07日
    浏览(10)
  • 一文了解Java序列化和反序列化:对象的存储与传输

    一文了解Java序列化和反序列化:对象的存储与传输

    Java序列化是一项强大而重要的技术,它允许我们将对象转换为字节流,以便在存储、传输和重建时使用。在本文中,我们将深入探讨Java序列化的基本概念、使用方法以及一些应用场景。 Java序列化是指将对象转换为字节流的过程,以便可以将其存储到文件、数据库或在网络上

    2024年02月13日
    浏览(15)
  • SharedPreferences工具类保存List对象,自动完成序列化和反序列化

    以下是一个示例的SharedPreferences工具类,其中包含了setList()和getList()方法,用于将List序列化为JSON字符串并存储到SharedPreferences中,以及从SharedPreferences中获取JSON字符串并反序列化为List对象: 在上述代码中,我们定义了一个SharedPreferencesUtils工具类,其中包含了setList()和getLis

    2024年02月16日
    浏览(12)
  • RabbitMQ 消息对象通过 Jackson 库消息转化器 序列化/反序列化 天坑!

    RabbitMQ 消息对象通过 Jackson 库消息转化器 序列化/反序列化 天坑!

    目录 1. 报错的背景 2. 问题分析 3. 最佳解决办法 a)使用 RabbitMQ 发送消息时,发送消息的类型为 MapString, Object,map 里面我 put 了一个 String, Long 类型,如下图: b)这里有一个前提:我清楚使用 org.springframework.amqp.rabbit.core.RabbitTemplate 来发送消息到 RabbitMQ 队列时,消息的序列

    2024年04月15日
    浏览(14)
  • 关于.netcore中序列化的特性

    在.NET Core中,有几个常用的序列化特性可以用来自定义对象的序列化和反序列化行为。下面是一些常用的序列化特性: [DataContract] 和 [DataMember] : 这两个特性用于在类上和属性上标记需要进行序列化的成员。使用DataContractSerializer进行序列化时,只有带有DataContract特性的类才会

    2024年02月10日
    浏览(8)
  • 关于fastjson序列化失败的问题

    关于fastjson序列化失败的问题

    com.alibaba.fastjson2.JSONArray cannot be cast to com.alibaba.fastjson2.JSONArray 我们使用若依的架子把common抽离出来一个jar包,导致从redis中序列化出错 这是由于springboot-devtools 使用restartClassLoader加载了JSONArray 而上层controller中的JSONArray依然是AppClassLoader,两个不同的类加载器加载的JSONArray没法

    2024年02月13日
    浏览(9)
  • 深入探究序列化与反序列化:原理、应用和最佳实践

    深入探究序列化与反序列化:原理、应用和最佳实践

    序列化 (Serialization)是指将对象转化为字节流的过程,以便于存储或通过网络进行传输。 反序列化 (Deserialization)则是将字节流转化为对象的过程,恢复原始对象的状态。 在计算机科学中,序列化和反序列化是常用的数据处理技术,用于在不同系统、不同编程语言之间传

    2024年02月08日
    浏览(11)
  • Redis反序列化--操作java对象

    Redis反序列化--操作java对象

    1.前言: 前段时间完成一个需求,上线之后发现每次发起请求服务器查询数据太慢了,组长便说加个两分钟的缓存。便打算使用reids做缓存,因为这个接口返回的是一个java对象,就遇到了序列化的问题。 针对数据的“序列化和反序列化”,提供了多种策略(RedisSerializer) 默认为

    2023年04月08日
    浏览(14)
  • System.Text.Json匿名对象反序列化

    以前就是一直使用 Newtonsoft.Json 用起来还是挺舒服的。由于 JSON 的应用越来越广,现在. NET Core 都内置了 System.Text.Json 可以直接对 JSON 进行操作,不过两个东西的体验依然有点区别。 有时候我们会遇到的从第三方传递过来的 json string 对象,对其进行解析并不需要所有的字段,

    2024年02月06日
    浏览(15)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包