保存对象(数据持久性)[重复]
- 2024-12-02 08:41:00
- admin 原创
- 174
问题描述:
我创建了一个这样的对象:
company1.name = 'banana'
company1.value = 40
我想保存此对象。我该怎么做?
解决方案 1:
您可以使用pickle
标准库中的模块。以下是将其基本应用于您的示例:
import pickle
class Company(object):
def __init__(self, name, value):
self.name = name
self.value = value
with open('company_data.pkl', 'wb') as outp:
company1 = Company('banana', 40)
pickle.dump(company1, outp, pickle.HIGHEST_PROTOCOL)
company2 = Company('spam', 42)
pickle.dump(company2, outp, pickle.HIGHEST_PROTOCOL)
del company1
del company2
with open('company_data.pkl', 'rb') as inp:
company1 = pickle.load(inp)
print(company1.name) # -> banana
print(company1.value) # -> 40
company2 = pickle.load(inp)
print(company2.name) # -> spam
print(company2.value) # -> 42
您还可以定义自己的简单实用程序,如下所示,打开一个文件并向其中写入单个对象:
def save_object(obj, filename):
with open(filename, 'wb') as outp: # Overwrites any existing file.
pickle.dump(obj, outp, pickle.HIGHEST_PROTOCOL)
# sample usage
save_object(company1, 'company1.pkl')
更新
由于这个答案非常受欢迎,因此我想谈谈一些稍微高级一点的使用主题。
cPickle
(或_pickle
)与pickle
几乎总是最好实际使用cPickle
模块,而不是pickle
因为前者是用 C 编写的并且速度更快。它们之间有一些细微的差别,但在大多数情况下它们是等效的,并且 C 版本将提供非常优越的性能。切换到它再简单不过了,只需将语句更改import
为:
import cPickle as pickle
在 Python 3 中,cPickle
被重命名_pickle
,但这样做不再是必要的,因为pickle
模块现在会自动执行此操作 - 请参阅python 3 中 pickle 和 _pickle 有什么区别?。
简而言之,您可以使用类似下面的方法确保当C 版本在 Python 2 和 3 中可用时,您的代码将始终使用 C 版本:
try:
import cPickle as pickle
except ModuleNotFoundError:
import pickle
数据流格式(协议)
pickle
可以读取和写入几种不同的 Python 特定格式的文件,这些格式称为协议,如文档中所述,“协议版本 0”是 ASCII,因此是“人类可读的”。版本 > 0 是二进制的,可用的最高版本取决于正在使用的 Python 版本。默认值还取决于 Python 版本。在 Python 2 中,默认值为协议版本0
,但在 Python 3.8.1 中,默认值为协议版本4
。在 Python 3.x 中,模块已添加pickle.DEFAULT_PROTOCOL
,但在 Python 2 中不存在。
幸运的是,每次调用时都有一种简写方式pickle.HIGHEST_PROTOCOL
(假设这就是你想要的,而且你通常都会这么做),只需使用文字数字-1
— 类似于通过负索引引用序列的最后一个元素。因此,不要这样写:
pickle.dump(obj, outp, pickle.HIGHEST_PROTOCOL)
你可以这样写:
pickle.dump(obj, outp, -1)
Pickler
无论哪种方式,如果您创建一个用于多个 pickle 操作的对象,则只需指定一次协议:
pickler = pickle.Pickler(outp, -1)
pickler.dump(obj1)
pickler.dump(obj2)
etc...
注意:如果您处于运行不同版本 Python 的环境中,那么您可能需要明确使用(即硬编码)所有版本都可以读取的特定协议号(更高版本通常可以读取早期版本生成的文件)。
多个对象
虽然 pickle 文件可以包含任意数量的 pickle 对象(如上例所示),但当它们的数量未知时,通常更容易将它们全部存储在某种大小可变的容器中,例如list
、tuple
或 ,dict
然后通过一次调用将它们全部写入文件:
tech_companies = [
Company('Apple', 114.18), Company('Google', 908.60), Company('Microsoft', 69.18)
]
save_object(tech_companies, 'tech_companies.pkl')
稍后使用以下命令恢复列表及其中的所有内容:
with open('tech_companies.pkl', 'rb') as inp:
tech_companies = pickle.load(inp)
主要优点是您不需要知道保存了多少个对象实例以便稍后重新加载它们(尽管在没有该信息的情况下也可以这样做,但它需要一些稍微专门的代码)。有关执行此操作的不同方法的详细信息,请参阅相关问题“在 pickle 文件中保存和加载多个对象?”的答案。我个人最喜欢@Lutz Prechelt 的答案,所以这是下面示例代码中使用的方法:
class Company:
def __init__(self, name, value):
self.name = name
self.value = value
def pickle_loader(filename):
""" Deserialize a file of pickled objects. """
with open(filename, "rb") as f:
while True:
try:
yield pickle.load(f)
except EOFError:
break
print('Companies in pickle file:')
for company in pickle_loader('company_data.pkl'):
print(' name: {}, value: {}'.format(company.name, company.value))
解决方案 2:
我认为,假设对象是 是一个相当强的假设class
。如果它不是 会怎么样class
?还有一种假设是,对象未在解释器中定义。如果它是在解释器中定义的会怎么样?此外,如果属性是动态添加的会怎么样?当某些 Python 对象在__dict__
创建后添加属性时,pickle
不会尊重这些属性的添加(即它“忘记”了它们的添加——因为pickle
通过引用对象定义进行序列化)。
在所有这些情况下,pickle
都可cPickle
能让你遭遇惨败。
如果您希望保存一个object
(任意创建的),其中您有属性(在对象定义中添加,或之后添加)......您最好的选择是使用dill
,它可以序列化 python 中的几乎任何内容。
我们从一堂课开始……
Python 2.7.8 (default, Jul 13 2014, 02:29:54)
[GCC 4.2.1 Compatible Apple Clang 4.1 ((tags/Apple/clang-421.11.66))] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import pickle
>>> class Company:
... pass
...
>>> company1 = Company()
>>> company1.name = 'banana'
>>> company1.value = 40
>>> with open('company.pkl', 'wb') as f:
... pickle.dump(company1, f, pickle.HIGHEST_PROTOCOL)
...
>>>
现在关机,然后重新启动...
Python 2.7.8 (default, Jul 13 2014, 02:29:54)
[GCC 4.2.1 Compatible Apple Clang 4.1 ((tags/Apple/clang-421.11.66))] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import pickle
>>> with open('company.pkl', 'rb') as f:
... company1 = pickle.load(f)
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pickle.py", line 1378, in load
return Unpickler(file).load()
File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pickle.py", line 858, in load
dispatch[key](self)
File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pickle.py", line 1090, in load_global
klass = self.find_class(module, name)
File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pickle.py", line 1126, in find_class
klass = getattr(mod, name)
AttributeError: 'module' object has no attribute 'Company'
>>>
哎呀…pickle
处理不了。我们试试吧。为了保险起见,dill
我们再加一个对象类型 (a )。lambda
Python 2.7.8 (default, Jul 13 2014, 02:29:54)
[GCC 4.2.1 Compatible Apple Clang 4.1 ((tags/Apple/clang-421.11.66))] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import dill
>>> class Company:
... pass
...
>>> company1 = Company()
>>> company1.name = 'banana'
>>> company1.value = 40
>>>
>>> company2 = lambda x:x
>>> company2.name = 'rhubarb'
>>> company2.value = 42
>>>
>>> with open('company_dill.pkl', 'wb') as f:
... dill.dump(company1, f)
... dill.dump(company2, f)
...
>>>
现在读取文件。
Python 2.7.8 (default, Jul 13 2014, 02:29:54)
[GCC 4.2.1 Compatible Apple Clang 4.1 ((tags/Apple/clang-421.11.66))] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import dill
>>> with open('company_dill.pkl', 'rb') as f:
... company1 = dill.load(f)
... company2 = dill.load(f)
...
>>> company1
<__main__.Company instance at 0x107909128>
>>> company1.name
'banana'
>>> company1.value
40
>>> company2.name
'rhubarb'
>>> company2.value
42
>>>
pickle
它有效。失败的原因dill
是,它像对待模块一样dill
对待__main__
(大多数情况下),并且还可以 pickle 类定义,而不是通过引用 pickle(像它所做的那样)。可以 pickle 的pickle
原因是它给它一个名字……然后 pickle 魔法就会发生。dill
`lambda`
实际上,有一种更简单的方法来保存所有这些对象,特别是如果你创建了很多对象。只需转储整个 python 会话,然后稍后再返回。
Python 2.7.8 (default, Jul 13 2014, 02:29:54)
[GCC 4.2.1 Compatible Apple Clang 4.1 ((tags/Apple/clang-421.11.66))] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import dill
>>> class Company:
... pass
...
>>> company1 = Company()
>>> company1.name = 'banana'
>>> company1.value = 40
>>>
>>> company2 = lambda x:x
>>> company2.name = 'rhubarb'
>>> company2.value = 42
>>>
>>> dill.dump_session('dill.pkl')
>>>
现在关闭您的计算机,去享用一杯浓咖啡或任何其他东西,然后稍后再回来......
Python 2.7.8 (default, Jul 13 2014, 02:29:54)
[GCC 4.2.1 Compatible Apple Clang 4.1 ((tags/Apple/clang-421.11.66))] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import dill
>>> dill.load_session('dill.pkl')
>>> company1.name
'banana'
>>> company1.value
40
>>> company2.name
'rhubarb'
>>> company2.value
42
>>> company2
<function <lambda> at 0x1065f2938>
唯一的主要缺点是它dill
不是 Python 标准库的一部分。因此,如果您无法在服务器上安装 Python 包,那么您就无法使用它。
但是,如果你能够在系统上安装 Python 包,你可以使用 获取最新版本dill
。git+https://github.com/uqfoundation/dill.git@master#egg=dill
并且你可以使用 获取最新发布的版本pip install dill
。
解决方案 3:
使用您的问题的快速示例company1
,使用 python3。
import pickle
# Save the file
pickle.dump(company1, file = open("company1.pickle", "wb"))
# Reload the file
company1_reloaded = pickle.load(open("company1.pickle", "rb"))
但是,正如这个答案所指出的,pickle 经常会失败。所以你真的应该使用dill
。
import dill
# Save the file
dill.dump(company1, file = open("company1.pickle", "wb"))
# Reload the file
company1_reloaded = dill.load(open("company1.pickle", "rb"))
解决方案 4:
你可以使用anycache来帮你完成这个工作。它考虑了所有的细节:
它使用dill作为后端,扩展了 python
pickle
模块来处理lambda
所有优秀的 python 功能。它将不同的对象存储到不同的文件中并正确地重新加载它们。
限制缓存大小
允许清除缓存
允许在多次运行之间共享对象
允许尊重影响结果的输入文件
myfunc
假设您有一个创建实例的函数:
from anycache import anycache
class Company(object):
def __init__(self, name, value):
self.name = name
self.value = value
@anycache(cachedir='/path/to/your/cache')
def myfunc(name, value)
return Company(name, value)
Anycachemyfunc
首次调用时cachedir
,使用唯一标识符(取决于函数名称及其参数)作为文件名将结果 pickle 到文件中。在任何连续运行中,都会加载 pickle 对象。如果cachedir
在 python 运行之间保留,则 pickle 对象取自上一次 python 运行。
更多详细信息请参阅文档
解决方案 5:
较新版本的 pandas 还具有保存 pickle 的功能。
我觉得这样更容易。例如
pd.to_pickle(object_to_save,'/temp/saved_pkl.pickle' )
解决方案 6:
虽然pickle
这是序列化对象最广泛使用的选项,但它并非没有问题,尤其是在安全性方面。可以构造一个 Python 对象,当该对象反序列化时,它将执行任意代码。下面提供了一个例子。
import pickle
class Example:
def __reduce__(self):
print("Serialised")
return print, ("Deserialised",)
example = Example()
serialised = pickle.dumps(example) # prints "Serialised"
pickle.loads(serialised) # prints "Deserialised"
虽然上面的例子完全无害,但可以很容易地使它变得更加糟糕,例如通过在返回值中print
替换exec
或。eval
对此并没有真正好的解决方案,但人们提到dill
库是 的可能替代方案pickle
。但是,还有另一种替代方案。marshal
它是一个内置序列化库,但它的缺点是它不适用于某些类型的对象(尽管与 不同pickle
,它可以序列化 CodeObjects,这就是为什么它在将 CodeObjects 编译为文件期间在内部用于序列化上述内容.pyc
)。它也更快,用 C 编写(代码在这里),但尚未经过严格审核。但是,没有已知的安全漏洞。
marshal
实际上是 的替代品pickle
。loads
、load
和方法dumps
都dump
做同样的事情。