Pickle
Python中有个序列化过程称为pickle,它能够实现任意对象与文本之间的相互转化,也可以实现任意对象与二进制之间的相互转化。也就是说,pickle可以实现Python对象的存储及恢复。
- 序列化(picking):把变量从内存中变成可存储或传输的过程称为序列化,序列化之后,就可以把序列化的对象写入磁盘,或者传输给其他设备
- 反序列化(unpickling):相应的,把变量的内容从序列化的对象重新读到内存里的过程称为反序列化
在机器学习中,我们常常需要把训练好的模型存储起来,这样在进行决策时直接将模型读出,而不需要重新训练模型,这样就大大节约了时间。Python提供的pickle模块就很好地解决了这个问题,它可以序列化对象并保存到磁盘中,并在需要的时候读取出来,任何对象都可以执行序列化操作。
Python2中有两个模块可以实现对象的序列化,pickle和cPickle,cPickle是用C语言实现的,pickle是用纯Python语言实现的,相比,cPickle的读写效率高一些。使用的时候,一般先尝试导入cPickle,如果失败,再导入pickle模块。
try: import cPickle as pickle except: import pickle
Python3种无需再这样进行导入:
A common pattern in Python 2.x is to have one version of a module implemented in pure Python, with an optional accelerated version implemented as a C extension; for example, pickle and cPickle. This places the burden of importing the accelerated version and falling back on the pure Python version on each user of these modules. In Python 3.0, the accelerated versions are considered implementation details of the pure Python versions. Users should always import the standard version, which attempts to import the accelerated version and falls back to the pure Python version. The pickle / cPickle pair received this treatment. The profile module is on the list for 3.1. The StringIO module has been turned into a class in the io module.https://docs.python.org/3.1/whatsnew/3.0.html#library-changes
pickle模块提供了以下4个函数供我们使用:
- dumps():将Python中的对象序列化成二进制对象,并返回
- loads():读取给定的二进制对象数据,并将其转换为Python对象
- dump():将Python中的对象序列化成二进制对象,并写入文件
- load():读取指定的序列化数据文件,并返回对象
以上这4个函数可以分成两类,其中dumps和loads实现基于内存的Python对象与二进制互转,dump和load实现基于文件的Python对象与二进制互转。
使用上与json序列化与反序列化类似,但中间还是存在一些区别:
- JSON只能存储文本形式的存储,Pickle可以存储成二进制
- JSON是人可读的,Pickle不可读
- JSON广泛应用于除Python外的其他领域,Pickle是Python独有的
- JSON只能dump一些python的内置对象,Pickle可以存储几乎所有对象
pickle的使用说明
pickle模块提供了两个常量
常量 | 说明 |
pickle.HIGHEST_PROTOCOL | 这是一个整数值,表示可用的最高协议版本。它可以作为协议版本的参数传递给dump()和dumps()函数 |
pickle.DEFAULT_PROTOCOL | 这是一个整数值,表示用于pickling的默认协议,其值可能小于最高协议的值 |
pickle模块提供的方法:
- dump(obj, file, protocol=None, *, fix_imports=True, buffer_callback=None)
- dumps(obj, protocol=None, *, fix_imports=True, buffer_callback=None)
- load(file, *, fix_imports=True, encoding=”ASCII”, errors=”strict”, buffers=None)
- loads(data, /, *, fix_imports=True, encoding=”ASCII”, errors=”strict”, buffers=None)
其中protocol可选参数:
- Protocol version 0 is the original “human-readable” protocol and is backwards compatible with earlier versions of Python.(原始的纯文本存储)
- Protocol version 1 is an old binary format which is also compatible with earlier versions of Python.(旧版二进制存储)
- Protocol version 2 was introduced in Python 2.3. It provides much more efficient pickling of new-style classes. Refer to PEP 307 for information about improvements brought by protocol 2.(新版二进制存储,效率更高,Python 2.3新增)
- Protocol version 3 was added in Python 3.0. It has explicit support for bytes objects and cannot be unpickled by Python 2.x. This was the default protocol in Python 3.0–3.7.(Python 3引入,在Python 3.0-3.7中默认)
- Protocol version 4 was added in Python 3.4. It adds support for very large objects, pickling more kinds of objects, and some data format optimizations. It is the default protocol starting with Python 3.8. Refer to PEP 3154 for information about improvements brought by protocol 4.(支持非常大的对象)
- Protocol version 5 was added in Python 3.8. It adds support for out-of-band data and speedup for in-band data. Refer to PEP 574 for information about improvements brought by protocol 5.
如果该参数传-1,则使用最高版本。
示例代码:
import pickle # take objects list, dictionary and class mylist = ['pink', 'green', 'blue', 'red'] mydict = {'a': 23, 'b': 17, 'c': 9} class Student: def __init__(self, name, age): self.name = name self.age = age def display_info(self): return ("Student name is {name}&is {age} years old".format(name=self.name, age=self.age)) # object created for student myobj = Student('Maria', 18) # pickling # bytestream of objects written in binary format pickle.dump(mylist, file=open('mylist.pkl', 'wb')) pickle.dump(mydict, file=open('mydict.pkl', 'wb')) pickle.dump(myobj, file=open('myobj.pkl', 'wb')) # delete objects del mylist del mydict del myobj # unpickling mylist = pickle.load(file=open('mylist.pkl', 'rb')) mydict = pickle.load(file=open('mydict.pkl', 'rb')) myobj = pickle.load(file=open('myobj.pkl', 'rb')) # printing objects and their types print('list object:', mylist, type(mylist)) print('dictionary object:', mydict, type(mydict)) print('student info:', myobj.display_info()) 输出内容: list object: ['pink', 'green', 'blue', 'red'] <class 'list'> dictionary object: {'a': 23, 'b': 17, 'c': 9} <class 'dict'> student info: Student name is Maria&is 18 years old
文件是否按照二进制方式打开好像影响不大,不过为了保险还是按它说的来比较好。
pickleDB
PickleDB和pickle是两个不同的工具,虽然它们的名称相似,但它们在用途和功能上有显著的区别。PickleDB是一个轻量级的键值数据库,旨在提供简单的持久化数据存储解决方案。使用JSON文件来存储数据,具有可读性和易于编辑的优点。
功能:
- 提供字典风格的API来进行数据的增删改查操作。
- 适合用于简单的应用场景,尤其是小型项目或原型开发。
特点:
- 无需复杂的数据库设置。
- 支持持久化存储,数据存储在磁盘上的JSON文件中。
pickleDB的示例:
>>> import pickledb >>> db = pickledb.load('test.db', False) >>> db.set('key', 'value') >>> db.get('key') 'value' >>> db.dump() True
pickleDB能否与pickle联合使用?测试代码:
# -*- coding: utf-8 -*- import pickledb import pickle import json dataList = [[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']] dataDic = {0: [1, 2, 3, 4], 1: ('a', 'b'), 2: {'c': 'yes', 'd': 'no'}} p1 = pickle.dumps(dataList) print(pickle.loads(p1)) p2 = pickle.dumps(dataDic) print(pickle.loads(p2)) db = pickledb.load('example.db', False) # 从文件加载数据库,如果没有会自动创建 db.set('p1', p1) # set设置一个键的字符串值 db.set('p2', p2) # set设置一个键的字符串值 print(pickle.loads(db.get('p1'))) # get获取一个键的值 print(pickle.loads(db.get('p2'))) # get获取一个键的值 db.dump() # 将数据库从内存保存到example.db
报如下错误:
--------------------------------------------------------------------------- JSONDecodeError Traceback (most recent call last) <ipython-input-6-201838b0e5b8> in <module> 18 print(pickle.loads(p2)) 19 ---> 20 db = pickledb.load('example.db', False) #从文件加载数据库,如果没有会自动创建 21 db.set('p1', p1) #set设置一个键的字符串值 22 db.set('p2', p2) #set设置一个键的字符串值 /opt/conda/lib/python3.7/site-packages/pickledb.py in load(location, auto_dump, sig) 41 def load(location, auto_dump, sig=True): 42 '''Return a pickledb object. location is the path to the json file.''' ---> 43 return PickleDB(location, auto_dump, sig) 44 45 /opt/conda/lib/python3.7/site-packages/pickledb.py in __init__(self, location, auto_dump, sig) 52 If the file does not exist it will be created on the first update. 53 ''' ---> 54 self.load(location, auto_dump) 55 self.dthread = None 56 if sig: /opt/conda/lib/python3.7/site-packages/pickledb.py in load(self, location, auto_dump) 83 self.auto_dump = auto_dump 84 if os.path.exists(location): ---> 85 self._loaddb() 86 else: 87 self.db = {} /opt/conda/lib/python3.7/site-packages/pickledb.py in _loaddb(self) 100 def _loaddb(self): 101 '''Load or reload the json info from the file''' --> 102 self.db = json.load(open(self.loco, 'rt')) 103 104 def _autodumpdb(self): /opt/conda/lib/python3.7/json/__init__.py in load(fp, cls, object_hook, parse_float, parse_int, parse_constant, object_pairs_hook, **kw) 294 cls = cls, object_hook = object_hook, 295 parse_float = parse_float, parse_int = parse_int, --> 296 parse_constant = parse_constant, object_pairs_hook = object_pairs_hook, **kw) 297 298 /opt/conda/lib/python3.7/json/__init__.py in loads(s, encoding, cls, object_hook, parse_float, parse_int, parse_constant, object_pairs_hook, **kw) 346 parse_int is None and parse_float is None and 347 parse_constant is None and object_pairs_hook is None and not kw): --> 348 return _default_decoder.decode(s) 349 if cls is None: 350 cls = JSONDecoder /opt/conda/lib/python3.7/json/decoder.py in decode(self, s, _w) 335 336 """ --> 337 obj, end = self.raw_decode(s, idx=_w(s, 0).end()) 338 end = _w(s, end).end() 339 if end != len(s): /opt/conda/lib/python3.7/json/decoder.py in raw_decode(self, s, idx) 353 obj, end = self.scan_once(s, idx) 354 except StopIteration as err: --> 355 raise JSONDecodeError("Expecting value", s, err.value) from None 356 return obj, end JSONDecodeError: Expecting value: line 1 column 1 (char 0)
貌似pickledb不支持和pickle混用。或是我的使用方法有误?
shelve
shelve是Python标准库中的一个模块,用于实现简单的持久化存储。它将Python对象存储在一个类似字典的持久化数据库中,这个数据库可以被轻松地读取和修改。shelve模块的优势在于它能够自动处理Python对象的序列化和反序列化,使得对象的持久化存储变得非常简单。
shelve模块的核心功能是将Python对象以键值对的形式存储在文件中,类似于字典的使用方式。这个文件存储在磁盘上,可以在程序的不同运行阶段打开和访问,持久化保存的数据可以随时恢复。
基本用法
shelve模块使用简单,主要提供了一个shelve.open()函数来打开一个存储文件。
shelve.open()函数
open()函数用于打开一个shelf文件,返回一个类似字典的对象,该对象可以用于存储和检索数据。
参数:
- filename:文件名,用于存储数据的文件。如果文件不存在,将会创建一个新文件。
- flag:可选参数,指定打开模式。常用值包括:
- ‘r’:只读模式。
- ‘w’:读写模式(默认)。
- ‘c’:读写模式,如果文件不存在则创建它(默认)。
- ‘n’:读写模式,总是创建一个新的空文件。
- protocol:可选参数,指定序列化协议版本,默认使用最高协议版本。
- writeback:可选参数,布尔值,控制是否在字典被修改后自动将修改写回到磁盘,默认为False。
- DbfilenameShelf和shelve.BsdDbShelf类:支持不同的底层数据库接口。
返回值:返回一个类字典对象,可以像字典一样操作。
示例:
import shelve #打开一个shelf文件进行读写 with shelve.open('mydata.db') as db: db['key1'] = {'name': 'Alice', 'age': 30} db['key2'] = [1, 2, 3, 4, 5] #读取数据 with shelve.open('mydata.db') as db: print(db['key1']) #输出:{'name': 'Alice', 'age': 30} print(db['key2']) #输出:[1, 2, 3, 4, 5]
工作机制
shelve模块背后依赖于Python的pickle模块来处理对象的序列化和反序列化。数据被存储在一个二进制文件中,并且可以被shelve文件作为字典的键值对存取。
读写模式
- 只读模式:在只读模式下打开的shelf文件只能读取数据,不能修改或添加新数据。
- 写回机制:如果writeback=True,所有读取的数据在内存中被修改后,会自动在关闭时写回到文件中。这种模式虽然方便,但可能会消耗更多内存和时间。
持久化存储
shelve使用底层数据库接口(如dbm)来实现持久化存储,这些接口将数据保存到磁盘上,而不是存储在内存中。因此,shelf文件可以在不同的Python会话间保持一致。
使用场景
shelve模块适用于需要持久化存储简单数据的场景,例如:
- 配置文件存储:将应用程序的配置参数保存到文件中,方便下次启动时读取。
- 数据缓存:将一些计算结果或中间数据存储到磁盘,以便在不同程序运行期间共享。
- 小型数据库:对于不需要完整数据库管理系统的小型应用,可以使用 shelve 存储和管理数据。
使用 shelve 存储和检索数据
import shelve # 打开一个 shelf 文件 with shelve.open('mystore') as store: # 存储数据 store['item1'] = 'Hello' store['item2'] = [1, 2, 3, 4] store['item3'] = {'key': 'value'} # 从 shelf 中读取数据 with shelve.open('mystore') as store: print(store['item1']) # 输出:Hello print(store['item2']) # 输出:[1, 2, 3, 4] print(store['item3']) # 输出:{'key': 'value'}
使用 writeback=True 自动写回
import shelve # 打开 shelve 文件,并启用写回机制 with shelve.open('mystore', writeback=True) as store: store['numbers'] = [1, 2, 3] # 修改读取的数据 store['numbers'].append(4) # 重新打开并检查修改是否保存 with shelve.open('mystore') as store: print(store['numbers']) # 输出:[1, 2, 3, 4]
实现简单的配置文件存储
import shelve def save_config(config, filename='config.db'): with shelve.open(filename) as db: db.update(config) def load_config(filename='config.db'): with shelve.open(filename) as db: return dict(db) # 示例配置数据 config_data = { 'username': 'admin', 'theme': 'dark', 'language': 'en' } # 保存配置数据 save_config(config_data) # 加载配置数据 loaded_config = load_config() print(loaded_config)
注意事项
- 数据一致性:如果在写操作后没有调用 sync() 或者 close(),数据可能不会被及时写回磁盘。
- 并发问题:shelve 本身不适用于多线程或多进程同时读写的场景。如果需要并发访问,应该考虑使用锁机制或其他数据库解决方案。
- 内存消耗:当 writeback=True 时,所有读取的数据都会保存在内存中,因此内存消耗可能较大。对大数据集使用时需谨慎。
copyreg
copyreg 和 pickle 是 Python 中两个相关但不同的模块,它们在序列化和反序列化过程中扮演着不同的角色。copyreg 模块用于自定义和扩展 pickle 的序列化和反序列化行为。特别是在处理自定义类的序列化时非常有用。
主要功能
- 允许开发者注册自定义的序列化和反序列化函数,以便 pickle 能够正确处理某些复杂或特殊的对象。
- 通过 pickle() 函数,开发者可以为特定类型指定如何序列化和反序列化。
使用场景
- 序列化自定义类:当你有一个复杂的类,其实例包含了不可直接序列化的对象时,可以使用 copyreg 自定义序列化逻辑。
- 跨版本兼容性:在不同版本的应用程序间传递数据时,可能需要定制序列化和反序列化行为,以确保不同版本间的数据兼容性。
- 优化序列化性能:通过自定义序列化方法,可以针对特定数据结构优化序列化过程,以提高性能。
copyreg 模块的核心功能是提供注册函数,以便自定义对象的序列化和反序列化行为。这些函数主要包括:
- 注册自定义的序列化函数:指定如何将自定义对象序列化为一个可以被 pickle 处理的形式。
- 注册自定义的反序列化函数:指定如何将 pickle 恢复的对象重建为原始对象。
主要函数
copyreg.pickle() 函数
copyreg.pickle() 函数是该模块的核心函数,用于为自定义类型注册序列化和反序列化函数。
参数:
- type:要注册的类型,即需要自定义序列化和反序列化行为的类或类型。
- pickle_function:用于序列化的函数,通常返回一个可被 pickle 处理的元组。
- constructor:可选参数,指向反序列化时使用的构造函数或类方法。
示例:
import copyreg import pickle class MyClass: def __init__(self, value): self.value = value # 自定义的序列化函数 def pickle_myclass(obj): return MyClass, (obj.value,) # 注册自定义的序列化和反序列化函数 copyreg.pickle(MyClass, pickle_myclass) # 序列化对象 obj = MyClass(42) serialized_obj = pickle.dumps(obj) # 反序列化对象 restored_obj = pickle.loads(serialized_obj) print(restored_obj.value) # 输出:42
工作机制
copyreg 模块通过注册序列化和反序列化函数,允许开发者控制特定类型对象的序列化和反序列化行为。通常,注册的 pickle_function 会返回一个元组,这个元组的第一个元素是构造函数或类,其余元素是该构造函数需要的参数。pickle 模块在反序列化时会根据这个元组来重建对象。
自定义序列化函数
自定义序列化函数应返回一个元组,第一个元素是用于反序列化的构造函数或类型,后续元素是该构造函数的参数。例如:
def pickle_myclass(obj): return MyClass, (obj.value,)
在反序列化时,pickle 会调用 MyClass(*args) 来重建对象。
反序列化
当使用 pickle.loads() 反序列化时,pickle 模块会根据 copyreg 中注册的构造函数,使用序列化函数返回的参数来重建对象。
简单的类序列化
import copyreg import pickle class Point: def __init__(self, x, y): self.x = x self.y = y def pickle_point(point): return Point, (point.x, point.y) # 注册Point类的自定义序列化和反序列化函数 copyreg.pickle(Point, pickle_point) # 序列化Point对象 p = Point(10, 20) serialized_p = pickle.dumps(p) # 反序列化Point对象 restored_p = pickle.loads(serialized_p) print(restored_p.x, restored_p.y) # 输出:10 20
带有不可序列化对象的类
import copyreg import pickle class MyClass: def __init__(self, value, callback): self.value = value self.callback = callback # 函数对象通常不可直接序列化 def pickle_myclass(obj): return MyClass, (obj.value, None) # 序列化时忽略回调函数 # 注册MyClass的自定义序列化函数 copyreg.pickle(MyClass, pickle_myclass) # 序列化对象 obj = MyClass(42, lambda: print("Hello")) serialized_obj = pickle.dumps(obj) # 反序列化对象 restored_obj = pickle.loads(serialized_obj) print(restored_obj.value) # 输出:42 print(restored_obj.callback) # 输出:None
参考链接: