python模块 pickle--python程序间的数据交换

本文使用的python3.8:https://docs.python.org/zh-cn/3.8/library/pickle.html#pickle.Pickler

1、pickle模块基础

  • pickle,用于python程序之间的数据交换。

1、pickle模块简介

  • 模块pickle实现了对一个Python对象结构的二进制序列化和反序列化。
  • "pickling"是将Python对象及其所拥有的层次结构转化为一个字节流的过程,而"unpickling"是相反的操作,会将(来自一个binaryfile或者bytes-likeobject的)字节流转化回一个对象层次结构。
  • pickling(和unpickling)也被称为“序列化”,“编组”或者“平面化”。而为了避免混乱,此处采用术语“封存(pickling)”和“解封(unpickling)”。

2、与json模块的比较

  • Pickle协议和JSON(JavaScript Object Notation)间有着本质的不同
  • JSON是一个文本序列化格式(它输出unicode文本,尽管在大多数时候它会接着以utf-8编码),而pickle是一个二进制序列化格式;
  • JSON是我们可以直观阅读的,而pickle不是;
  • JSON是可互操作的,在Python系统之外广泛使用,而pickle则是Python专用的;
  • 默认情况下,JSON只能表示Python内置类型的子集,不能表示自定义的类;但pickle可以表示大量的Python数据类型(可以合理使用Python的对象内省功能自动地表示大多数类型,复杂情况可以通过实现 specific object APIs 来解决)。
  • pickle模块“并不安全”。只应该对信任的数据进行unpickle操作。
    构建恶意的pickle数据“在解封时执行任意代码”都是可能的。绝对不要对不信任来源的数据和可能被篡改过的数据进行解封。
    对一个不信任的JSON进行反序列化的操作本身不会造成任意代码执行漏洞。

3、数据流格式

  • 默认情况下,pickle格式使用相对紧凑的二进制来存储。如果需要让文件更小,可以高效地压缩由pickle封存的数据。
  • pickle当前共有6种不同的协议(v0、v1、v2、v3、v4、v5)可用于封存操作。使用的协议版本越高,读取所生成pickle对象所需的Python版本就要越新。
  • 序列化是一种比持久化更底层的概念,虽然pickle读取和写入的是文件对象,但它不处理持久对象的命名问题,也不处理对持久对象的并发访问(甚至更复杂)的问题。pickle模块可以将复杂对象转换为字节流,也可以将字节流转换为具有相同内部结构的对象。处理这些字节流最常见的做法是将它们写入文件,但它们也可以通过网络发送或存储在数据库中。shelve模块提供了一个简单的接口,用于在DBM类型的数据库文件上封存和解封对象。

2、pickle模块常量和方法

  • 序列化某个包含层次结构的对象,只需调用dumps()函数即可。同样,要反序列化数据流,可以调用loads()函数。但是,如果要对序列化和反序列化加以更多的控制,可以分别创建Pickler或Unpickler对象。

1、pickle模块的常量

  • pickle.HIGHEST_PROTOCOL

整数,可用的pickle的最高协议版本。此值可以作为协议值传递给dump()和dumps()函数,以及Pickler的构造函数。

  • pickle.DEFAULT_PROTOCOL

整数,用于pickle数据的默认协议版本。它可能小于HIGHEST_PROTOCOL。当前默认协议是v4,它在Python3.4中首次引入,与之前的版本不兼容。
在3.0版:默认协议版本是3。
在3.8版:默认协议版本是4

2、pickle模块的方法

1、pickle.dump方法(封存对象,保存到文件中)

pickle.dump(obj, file, protocol=None, *, fix_imports=True, buffer_callback=None)

  • 将对象obj封存以后的对象写入已打开的文件对象file object file)。它等同于Pickler(file, protocol).dump(obj)。
  • 参数file必须有一个write()方法,该write()方法要能接收字节作为其唯一参数。因此,它可以是一个打开的磁盘文件(用于写入二进制内容),也可以是一个io.BytesIO实例,也可以是满足这一接口的其他任何自定义对象。
  • 可选参数protocol是一个整数,告知pickler使用指定的协议,可选择的协议范围从0到HIGHEST_PROTOCOL。如果没有指定,这一参数默认值为DEFAULT_PROTOCOL。指定一个负数就相当于指定HIGHEST_PROTOCOL。
  • 如果fix_imports为True且protocol小于3,pickle将尝试将Python3中的新名称映射到Python2中的旧模块名称,因此Python2也可以读取封存的数据流。
  • 如果buffer_callback为None(默认情况),缓冲区视图(bufferview)将会作为pickle流的一部分被序列化到file中。
    如果buffer_callback不为None,那它可以用缓冲区视图调用任意次。如果某次调用返回了False值(例如None),则给定的缓冲区是带外的,否则缓冲区是带内的(例如保存在了pickle流里面)。
    如果buffer_callback不是None且protocol是None或小于5,就会出错。
    在3.8版:加入了buffer_callback参数。

2、pickle.dumps方法(封存对象)

pickle.dumps(obj, protocol=None, *, fix_imports=True, buffer_callback=None)

  • 将obj封存以后的对象作为bytes类型直接返回,而不是将其写入到文件
  • 参数protocol、fix_imports和buffer_callback的含义与它们在dump函数中的含义相同。
  • 在3.8版:加入了buffer_callback参数。

3、pickle.load方法(解封,从文件中读取)

pickle.load(file, *, fix_imports=True, encoding="ASCII", errors="strict", buffers=None)

  • 已打开的file object文件中读取封存后的对象,重建其中特定对象的层次结构并返回。它相当于Unpickler(file).load()。
  • Pickle协议版本是自动检测出来的,所以不需要参数来指定协议。封存对象以外的其他字节将被忽略。
  • 参数file必须有三个方法,read()方法接受一个整数参数,readinto()方法接受一个缓冲区作为参数,readline()方法不需要参数,这与io.BufferedIOBase里定义的接口是相同的。因此file可以是一个磁盘上用于二进制读取的文件,也可以是一个io.BytesIO实例,也可以是满足这一接口的其他任何自定义对象。
  • 可选的参数是fix_imports,encoding和errors,用于控制由Python2生成的pickle流的兼容性。如果fix_imports为True,则pickle将尝试将旧的Python2名称映射到Python3中对应的新名称。encoding和errors参数告诉pickle如何解码Python2存储的8位字符串实例;这两个参数默认分别为'ASCII'和'strict'。encoding参数可置为'bytes'来将这些8位字符串实例读取为字节对象。读取NumPy array和Python2存储的datetime、date和time实例时,请使用encoding='latin1'。
  • 如果buffers为None(默认值),则反序列化所需的所有数据都必须包含在pickle流中。这意味着在实例化Pickler时(或调用dump()或dumps()时),参数buffer_callback为None。
    如果buffers不为None,则每次pickle流引用带外缓冲区视图时,消耗的对象都应该是可迭代的启用缓冲区的对象。这样的缓冲区应该按顺序地提供给Pickler对象的buffer_callback方法。
    在3.8版:加入了buffers参数。

4、pickle.loads方法(解封)

pickle.loads(data, *, fix_imports=True, encoding="ASCII", errors="strict", buffers=None)

  • 重建并返回一个对象的封存表示形式data的对象层级结构。data必须为bytes-like object
  • Pickle协议版本是自动检测出来的,所以不需要参数来指定协议。封存对象以外的其他字节将被忽略。
  • 参数file、fix_imports、encoding、errors、strict和buffers的含义与它们在load中的含义相同。
    在3.8版更改:加入了buffers参数。

3、可以被封存/解封的对象

  • 尝试封存不能被封存的对象会抛出PicklingError异常,异常发生时,可能有部分字节已经被写入指定文件中。

1、python中可以被pickle封存的类型

  • None、True和False
  • 整数、浮点数、复数
  • str、byte、bytearray
  • 只包含可封存对象的集合,包括tuple、list、set和dict
  • 定义在模块最外层的函数(使用def定义,lambda函数则不可以)
  • 定义在模块最外层的内置函数
  • 定义在模块最外层的类
  • 某些类实例,这些类的__dict__属性值或__getstate__()函数的返回值可以被封存

2、封存函数、类和实例

  • 函数(内置函数或用户自定义函数)在被封存时,引用的是函数全名。这意味着只有函数所在的模块名,与函数名会被封存函数体及其属性不会被封存。因此,在解封的环境中,函数所属的模块必须是可以被导入的,而且模块必须包含这个函数被封存时的名称,否则会抛出异常。
  • 也只封存名称,所以在解封环境中也有和函数相同的限制。注意,类体及其数据不会被封存
  • 在封存类的实例时,其类体和类数据不会跟着实例一起被封存,只有实例数据会被封存

1、封存函数

示例1:pickle.dumps和pickle.loads

import pickle
def func():
    print('1 + 2 =', 1 + 2)

pickledumps = pickle.dumps(func)         #封存
pickloads = pickle.loads(pickledumps)    #解封

a = pickloads
a()

<<<
1 + 2 = 3

示例2:pickle.dump和pickle.load(此文件名是aa.py)

import pickle
def func():
    print('1 + 2 =', 1 + 2)

with open('text.txt', 'wb') as file:
    pickledump = pickle.dump(func, file)    #封存

if __name__ == '__main__':
    with open('text.txt', 'rb') as file:
        pickload = pickle.load(file)        #解封
    a = pickload
    a()

<<<
1 + 2 = 3
  • 在另一个.py文件中解封
import pickle
import aa                                  #不导入aa,异常AttributeError: Can't get attribute 'func',因为封存函数时不封存函数体
with open('text.txt', 'rb') as file:
    pickload = pickle.load(file)           #解封

a = pickload
a()

<<<
1 + 2 = 3

2、封存类

示例1:pickle.dumps和pickle.loads

import pickle
class Foo:
    attr = 'A class attribute'

pickledumps = pickle.dumps(Foo)          #封存类
pickloads = pickle.loads(pickledumps)    #解封

a = pickloads()
print(a.attr)

<<<
A class attribute

示例2:pickle.dump和pickle.load(此文件名是aa.py)

import pickle
class Foo:
    attr = 'A class attribute'

with open('text.txt', 'wb') as file:
    pickle.dump(Foo, file)               #封存类,并保存进文件中

if __name__ == '__main__':
    with open('text.txt', 'rb') as file:
        pickload = pickle.load(file)     #从文件中读取,并解封
    a = pickload()
    print(a.attr)

<<<
A class attribute
  • 在另一个.py文件中解封
import pickle
import aa                              #不导入aa,异常AttributeError: Can't get attribute 'Foo',因为封存类时不封存类体
with open('text.txt', 'rb') as file:
    pickload = pickle.load(file)       #解封

a = pickload()
print(a.attr)

<<<
A class attribute

3、封存类的实例

  • 通常,一个实例被封存不需要附加任何代码。Pickle默认会通过Python的内省机制获得实例的类及属性。
  • 当实例解封时,它的__init__()方法通常不会被调用。其默认动作是:先创建一个未初始化的实例,然后还原其属性。

示例1:

import pickle
class Foo:
    attr = 'A class attribute'
    def __init__(self, name, age):
        self.name = name
        self.age = age

aa = Foo('heng', 11)
bb = Foo('ha', 22)
pickdumpaa = pickle.dumps(aa)            #封存实例aa
pickdumpbb = pickle.dumps(bb)            #封存实例bb
pickloadaa = pickle.loads(pickdumpaa)    #解封
pickloadbb = pickle.loads(pickdumpbb)    #解封

print(pickloadaa.name, pickloadaa.age)
print(pickloadbb.name, pickloadbb.age)

<<<
heng 11
ha 22

示例2:pickle.dump和pickle.load(此文件名是aa.py)

import pickle
class Foo:
    attr = 'A class attribute'
    def __init__(self, name, age):
        self.name = name
        self.age = age

aa = Foo('heng', 11)
bb = Foo('ha', 22)

with open('text1.txt', 'wb') as file:
    pickle.dump(aa, file)                #封存实例aa,并保存进文件中
with open('text2.txt', 'wb') as file:
    pickle.dump(bb, file)                #封存实例bb,并保存进文件中

if __name__ == '__main__':
    with open('text1.txt', 'rb') as file:
        pickload1 = pickle.load(file)    #从文件中读取,并解封
    with open('text2.txt', 'rb') as file:
        pickload2 = pickle.load(file)    #从文件中读取,并解封
    aa1 = pickload1
    print(aa1.name, aa1.age)
    bb2 = pickload2
    print(bb2.name, bb2.age)

<<<
heng 11
ha 22
  • 在另一个.py文件中解封
import pickle
import aa

with open('text1.txt', 'rb') as file:
    pickload1 = pickle.load(file)          #解封
with open('text2.txt', 'rb') as file:
    pickload2 = pickle.load(file)          #解封

a11 = pickload1
print(a11.name, a11.age)
b22 = pickload2
print(b22.name, b22.age)

<<<
heng 11
ha 22

 

posted @ 2021-05-25 00:45  麦恒  阅读(583)  评论(0编辑  收藏  举报