[python] ​Python数据序列化模块pickle使用笔记

pickle是一个Python的内置模块,用于在Python中实现对象结构序列化和反序列化。Python序列化是一个将Python对象层次结构转换为可以本地存储或者网络传输的字节流的过程,反序列化则是将字节流还原为将Python对象层次结构。

数据序列化的功能简单理解为把不能直接存储的数据存储到磁盘中,从而延长对象的生命周期。Python的常用序列化库有两个,即json和pickle。json库和pickle库的主要区别有两点:

  • pickle可以序列化Python中所有的数据类型,包括类,函数,一般存储为二进制文件。而json只能序列化Python基本的数据类型,转储结果非常容易阅读。
  • pickle只能在Python中使用,而json是能够在不同语言之间交换数据的。

pickle一般情况下比json慢,尤其是数据量很大的情况下。pickle和json都有四种基础方法:

方法作用
dump序列化写入文件
load读取文件反序列化
dumps序列化返回对象
loads反序列化对象

1 pickle使用

pickle.dump()函数用于将python结构序列化,并存为二进制文件。 pickle.dump函数接受三个参数,其中第一个参数包含要存储在文件中的对象,第二个参数给出以二进制模式写入所需文件时获得的文件对象。第三个参数表示序列化协议。

对于pickle的协议选取,目前有5种不同的协议可用(出自Python object serialization)。使用的协议越高,读取生成的pickle所需的Python版本越新。这些协议包括:

  • 协议版本0是原始的“人类可读”协议,与Python的早期版本向后兼容。
  • 协议版本1是一种旧的二进制格式,也与Python的早期版本兼容。
  • 协议版本2于Python2.3引入,提供了更为有效的序列化方式。
  • 协议版本3于Python3.0引入。它明确支持bytes对象,这也是Python的默认协议,也是需要与其他Python3版本兼容时的推荐协议。
  • 协议版本4于Python3.4引入。它增加了对超大对象的支持,对更多类型的对象进行序列化,并对一些数据格式优化。

通过0到4可以设置不同的协议,该协议参数默认为None,None表示使用Python版本使用的默认协议。选择-1表示最高协议。此外可以通过常量设置该协议,分别是:

  • pickle.HIGHEST_PROTOCOL:表示最高协议。
  • pickle.DEFAULT_PROTOCOL:表示默认协议。
import pickle
print("当前python环境最高序列化协议版本为:{}".format(pickle.HIGHEST_PROTOCOL))
print("当前python环境默认序列化协议版本为:{}".format(pickle.DEFAULT_PROTOCOL))
当前python环境最高序列化协议版本为:4
当前python环境默认序列化协议版本为:3
# 序列化实例
import pickle
import numpy as np

data = {
    "name": "data struct",
    "number": 123.456,
    "tuple": ("first", False, 10.01),
    "numpy_data": np.ones((9,9),np.uint8)
}

# 保存到本地,这个文件名包含后缀可以随意命名,反正是二进制文件
with open('data.bin', 'wb') as f:
    # 设置最底层协议
    pickle.dump(data, f, 0)

# 查看文件大小
!du -h data.bin
print('---分界线---')
# 查看文件前十行,发现有可读文字
!cat data.bin | head -n 5
4.0K	data.bin
---分界线---
(dp0
Vname
p1
Vdata struct
p2
# 保存到本地,这个文件名包含后缀可以随意命名,反正是二进制文件
with open('data.bin', 'wb') as f:
    # 设置最底层协议
    pickle.dump(data, f, 1)

# 查看文件大小
!du -h data.bin
print('---分界线---')
# 查看文件前2行
!cat data.bin | head -n 2
4.0K	data.bin
---分界线---
}q (X   nameqX   data structqX   numberqG@^�/��wX   tupleq(X   firstqI00
G@$�Q�tqX
# 保存到本地,这个文件名包含后缀可以随意命名,反正是二进制文件
with open('data.bin', 'wb') as f:
    # 设置默认协议
    pickle.dump(data, f, pickle.DEFAULT_PROTOCOL)

# 查看文件大小
!du -h data.bin
print('---分界线---')
# 查看文件前2行
!cat data.bin | head -n 2
4.0K	data.bin
---分界线---
�}q (X   nameqX   data structqX   numberqG@^�/��wX   tupleqX   firstq�G@$�Q녇qX
   numpy_dataqcnumpy.core.multiarray
# 保存到本地,这个文件名包含后缀可以随意命名,反正是二进制文件
with open('data.bin', 'wb') as f:
    # 设置默认协议
    pickle.dump(data, f, 4)

# 查看文件大小
!du -h data.bin
print('---分界线---')
# 查看文件前2行
!cat data.bin | head -n 2
4.0K	data.bin
---分界线---
��/      }�(�name��data struct��number�G@^�/��w�tuple��first��G@$�Q녇��
numpy_data��numpy.core.multiarray��_reconstruct����numpy��ndarray���K ��Cb���R�(KK	K	��h�dtype����u1�����R�(K�|�NNNJ����J����K t�b�CQ�t�bu.

如果想反序列化,重新读入文件,直接用pickle.load函数就行了。序列化协议是自动检测的,不需要指定。此外还有两个参数encoding和errors告诉pickle如何反序列低于当前python版本的序列化文件,默认值就行了。

import pickle

with open('data.bin', 'rb') as f:
    data = pickle.load(f)
    print(type(data))
    print(data['name'])
    print(data.keys())
<class 'dict'>
data struct
dict_keys(['name', 'number', 'tuple', 'numpy_data'])

通过dumps函数将对象的序列化表示作为bytes对象返回,而不是将其写入文件。通过loads函数则将bytes对象反序列化。注意bytes是 Python3新增的类型,bytes只负责以二进制形式来存储数据。

data = [1,2,3]

# 序列化,返回bytes对象
dumped = pickle.dumps(data)
print(dumped)
print(type(dumped))
print(len(dumped))

# 反序列化
loaded = pickle.loads(dumped)
print(loaded)
b'\x80\x03]q\x00(K\x01K\x02K\x03e.'
<class 'bytes'>
14
[1, 2, 3]

序列化和反序列化的过程可以通过__getstate__ 和__setstate__函数来影响。其中__getstate__函数在序列化时调用,__setstate__函数在反序列化时调用。

一个实例如下,在序列化时指定序列化某些参数,反序列化时恢复参数。

import pickle

class MyData:

    def __init__(self, x):

        self.x = x
        self.y = self.sqrt(x)

    def sqrt(self,x):
        return x**x

    def __getstate__(self):
        self.state = "ok"
        print("enter getstate")
        #  self.__dict__存储关于self.xxx的一些东西
        odict = self.__dict__.copy()
        del odict['y']
        print(odict)
        return odict

    def __setstate__(self, input):
        print("enter setstate")
        print(input)
        self.x = input['x']
        self.y = self.sqrt(self.x)

obj = MyData(3)
# 序列化
print("序列化")
dumped = pickle.dumps(obj)
# 反序列化
print("反序列化")
loaded = pickle.loads(dumped)
print("反序列化结果", loaded.y)
序列化
enter getstate
{'x': 3, 'state': 'ok'}
反序列化
enter setstate
{'x': 3, 'state': 'ok'}
反序列化结果 27

2 pickle加速

当要序列化的对象特别大时,pickle加载和保存序列化对象会成为代码的性能瓶颈。一般有三种办法加速pickle序列化过程。主要有:

  • 使用更高的协议版本
  • 使用cPickle代替pickle
  • 禁用垃圾收集器

下面几个例子会给出使用方法,不过加速效果不明显,因为数据量不大,写个代码mark下。

直接使用pickle

import time
import pickle
import numpy as np
import os 
def time_count(func):
    def inner(*args,**kwargs):
        start = time.time()
        func(*args,**kwargs)
        end = time.time()
        print('{}用时:{}秒'.format(func.__name__,end-start))
    return inner

@time_count
def pickle_dump(data,filepath):
    with open(filepath, 'wb') as f:
        pickle.dump(data, f)


@time_count
def pickle_load(filepath):
    with open(filepath, 'rb') as f:
        data = pickle.load(f)
    return data

data = np.ones((10000, 10000))
filepath = "file.dat"
pickle_dump(data,filepath)
pickle_load(filepath)
os.remove(filepath)
time.sleep(2)
pickle_dump用时:1.7647628784179688秒
pickle_load用时:1.7913622856140137秒

使用pickle最高协议

将参数协议指定为-1,即可,但是加速可能效果不明显。具体看数据。

import time
import pickle
import numpy as np
import os

def time_count(func):
    def inner(*args,**kwargs):
        start = time.time()
        func(*args,**kwargs)
        end = time.time()
        print('{}用时:{}秒'.format(func.__name__,end-start))
    return inner

@time_count
def pickle_dump(data,filepath):
    with open(filepath, 'wb') as f:
        # 使用最高版本协议
        pickle.dump(data, f, -1)


@time_count
def pickle_load(filepath):
    with open(filepath, 'rb') as f:
        data = pickle.load(f)
    return data

data = np.ones((10000, 10000))
filepath = "file.dat"
pickle_dump(data,filepath)
pickle_load(filepath)
os.remove(filepath)
time.sleep(2)
pickle_dump用时:1.731525182723999秒
pickle_load用时:1.7664134502410889秒

用cPickle代替pickle

最简单方式是使用cPickle而不是pickle。cPickle与pickle是完全相同的模块,具有相同的功能、相同的参数。唯一区别是cPickle用C语言编写的,这使cPickle速度更快。

import time
# python3 导入cPickle方式
import _pickle as cPickle
import numpy as np
import os

def time_count(func):
    def inner(*args,**kwargs):
        start = time.time()
        func(*args,**kwargs)
        end = time.time()
        print('{}用时:{}秒'.format(func.__name__,end-start))
    return inner

@time_count
def pickle_dump(data,filepath):
    with open(filepath, 'wb') as f:
        # 使用最高版本协议
        cPickle.dump(data, f, -1)


@time_count
def pickle_load(filepath):
    with open(filepath, 'rb') as f:
        data = cPickle.load(f)
    return data

data = np.ones((10000, 10000))
filepath = "file.dat"
pickle_dump(data,filepath)
pickle_load(filepath)
os.remove(filepath)
time.sleep(2)
pickle_dump用时:1.7443737983703613秒
pickle_load用时:1.7894999980926514秒

禁用垃圾回收

垃圾收集器会减慢处理速度,禁用它可以提高性能。

import time
import pickle
import numpy as np
import os
import gc

# 禁用垃圾回收
gc.disable()

def time_count(func):
    def inner(*args,**kwargs):
        start = time.time()
        func(*args,**kwargs)
        end = time.time()
        print('{}用时:{}秒'.format(func.__name__,end-start))
    return inner

@time_count
def pickle_dump(data,filepath):
    with open(filepath, 'wb') as f:
        # 使用最高版本协议
        pickle.dump(data, f, -1)


@time_count
def pickle_load(filepath):
    with open(filepath, 'rb') as f:
        data = pickle.load(f)
    return data

data = np.ones((10000, 10000))
filepath = "file.dat"
pickle_dump(data,filepath)
pickle_load(filepath)
os.remove(filepath)
time.sleep(2)

# 开启垃圾回收
gc.enable()
pickle_dump用时:1.8271889686584473秒
pickle_load用时:1.7800366878509521秒

3 参考

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Pythonpickle模块是用来实现序列的,即将Python中的对象转换成字节流,方便存储和传输。pickle模块支持多种协议,其中协议0是最早的版本,协议1和协议2是Pyhton2中引入的,协议3是Python3.0中引入的,协议4是Python3.4中引入的,每个协议都有其特点和适用范围。 下面我们来详细了解一下pickle模块使用方法和各个协议的特点。 ## 基本用法 pickle模块提供了dumps、dump、loads和load四个函数,分别用来进行序列和反序列操作。其中dumps和loads函数可以直接将对象转换成字节流或将字节流转换成对象,而dump和load函数则可以将对象序列到文件或从文件中反序列对象。 ### 序列Python对象转换成字节流的过程称为序列,可以使用dumps函数实现: ```python import pickle data = {'name': 'Tom', 'age': 18, 'gender': 'male'} bytes_data = pickle.dumps(data) print(bytes_data) ``` 输出结果为: ``` b'\x80\x04\x95\x17\x00\x00\x00\x00\x00\x00\x00}\x94(\x8c\x04name\x94\x8c\x03Tom\x94\x8c\x03age\x94K\x12\x8c\x06gender\x94\x8c\x04male\x94u.' ``` 可以看到,data字典被转换成了一串二进制的字节流。 ### 反序列 将字节流转换成Python对象的过程称为反序列,可以使用loads函数实现: ```python import pickle bytes_data = b'\x80\x04\x95\x17\x00\x00\x00\x00\x00\x00\x00}\x94(\x8c\x04name\x94\x8c\x03Tom\x94\x8c\x03age\x94K\x12\x8c\x06gender\x94\x8c\x04male\x94u.' data = pickle.loads(bytes_data) print(data) ``` 输出结果为: ``` {'name': 'Tom', 'age': 18, 'gender': 'male'} ``` ### 文件操作 除了使用dumps和loads函数进行序列和反序列操作外,pickle模块还提供了dump和load函数用于将对象序列到文件或从文件中反序列对象。 将对象序列到文件: ```python import pickle data = {'name': 'Tom', 'age': 18, 'gender': 'male'} with open('data.pkl', 'wb') as f: pickle.dump(data, f) ``` 从文件中反序列对象: ```python import pickle with open('data.pkl', 'rb') as f: data = pickle.load(f) print(data) ``` ## 协议0 协议0是最早的版本,它使用ASCII码来表示序列后的对象,因此序列后的数据比较大。使用协议0时,可以指定文件打开模式为't',表示以文本模式打开文件: ```python import pickle data = {'name': 'Tom', 'age': 18, 'gender': 'male'} with open('data.pkl', 'wt') as f: pickle.dump(data, f, protocol=0) with open('data.pkl', 'rt') as f: data = pickle.load(f) print(data) ``` 输出结果为: ``` {'age': 18, 'gender': 'male', 'name': 'Tom'} ``` ## 协议1 协议1和协议2是Python2中引入的,它们使用更紧凑的二进制格式表示序列后的对象。协议1可以指定文件打开模式为'wb',表示以二进制模式打开文件: ```python import pickle data = {'name': 'Tom', 'age': 18, 'gender': 'male'} with open('data.pkl', 'wb') as f: pickle.dump(data, f, protocol=1) with open('data.pkl', 'rb') as f: data = pickle.load(f) print(data) ``` 输出结果为: ``` {'name': 'Tom', 'age': 18, 'gender': 'male'} ``` ## 协议2 协议2是协议1的改进版本,它支持新的对象类型,如集合、字典等。在Python2中,协议2是默认使用的协议,如果不指定协议号,则使用协议2。 在Python3中,pickle模块默认使用协议3,但仍然可以使用协议2: ```python import pickle data = {'name': 'Tom', 'age': 18, 'gender': 'male'} with open('data.pkl', 'wb') as f: pickle.dump(data, f, protocol=2) with open('data.pkl', 'rb') as f: data = pickle.load(f) print(data) ``` 输出结果为: ``` {'name': 'Tom', 'age': 18, 'gender': 'male'} ``` ## 协议3 协议3是Python3.0中引入的,它支持更多的对象类型,如bytes、bytearray、set等。在Python3中,协议3是默认使用的协议,因此可以省略protocol参数: ```python import pickle data = {'name': 'Tom', 'age': 18, 'gender': 'male'} with open('data.pkl', 'wb') as f: pickle.dump(data, f) with open('data.pkl', 'rb') as f: data = pickle.load(f) print(data) ``` 输出结果为: ``` {'name': 'Tom', 'age': 18, 'gender': 'male'} ``` ## 协议4 协议4是Python3.4中引入的,它支持更多的对象类型,如memoryview、tuple等。协议4还支持从流中读取指定长度的数据,从而避免了一次性读取太多数据导致内存溢出的问题。 使用协议4时,需要将文件打开模式指定为'xb',表示以二进制模式打开文件,并且不能使用文本模式: ```python import pickle data = {'name': 'Tom', 'age': 18, 'gender': 'male'} with open('data.pkl', 'xb') as f: pickle.dump(data, f, protocol=4) with open('data.pkl', 'rb') as f: data = pickle.load(f) print(data) ``` 输出结果为: ``` {'name': 'Tom', 'age': 18, 'gender': 'male'} ``` ## 注意事项 在使用pickle模块时,需要注意以下几点: - 序列和反序列的对象必须是可序列的,即不能包含不能序列的对象。 - 序列和反序列的对象必须是相同的类型,否则可能会出现错误。 - 序列和反序列的对象必须是可信的,否则可能会被注入恶意代码。 - 不同协议之间的兼容性不同,不同协议之间的序列和反序列操作不一定是互逆的。因此,在使用不同协议时,需要注意协议号的兼容性和相应的操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值