问题:保存对象(数据持久性)
我创建了一个像这样的对象:
company1.name = 'banana'
company1.value = 40
我想保存该对象。我怎样才能做到这一点?
回答 0
您可以使用pickle
标准库中的模块。这是您的示例的基本应用:
import pickle
class Company(object):
def __init__(self, name, value):
self.name = name
self.value = value
with open('company_data.pkl', 'wb') as output:
company1 = Company('banana', 40)
pickle.dump(company1, output, pickle.HIGHEST_PROTOCOL)
company2 = Company('spam', 42)
pickle.dump(company2, output, pickle.HIGHEST_PROTOCOL)
del company1
del company2
with open('company_data.pkl', 'rb') as input:
company1 = pickle.load(input)
print(company1.name) # -> banana
print(company1.value) # -> 40
company2 = pickle.load(input)
print(company2.name) # -> spam
print(company2.value) # -> 42
您还可以定义自己的简单实用程序,如下所示,该实用程序打开文件并向其中写入单个对象:
def save_object(obj, filename):
with open(filename, 'wb') as output: # Overwrites any existing file.
pickle.dump(obj, output, 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有什么区别?。
总结是,您可以使用类似以下内容的代码来确保您的代码在Python 2和3中都可用时始终使用C版本:
try:
import cPickle as pickle
except ModuleNotFoundError:
import pickle
数据流格式(协议)
pickle
可以读写多种不同的特定于Python的格式的文件,称为文档中所述的协议,“协议版本0”为ASCII,因此“易于阅读”。> 0的版本是二进制的,可用的最高版本取决于所使用的Python版本。默认值还取决于Python版本。在Python 2中,默认值是Protocol版本,但在Python 3.8.1中,它是Protocol版本。在Python 3.x中,该模块已添加,但在Python 2中不存在。0
4
pickle.DEFAULT_PROTOCOL
幸运的是,pickle.HIGHEST_PROTOCOL
在每个调用中都有一个写速记的方法(假设这就是您想要的,并且您通常会这样做),只需使用文字数字-1
-类似于通过负索引引用序列的最后一个元素。因此,与其编写:
pickle.dump(obj, output, pickle.HIGHEST_PROTOCOL)
您可以这样写:
pickle.dump(obj, output, -1)
无论哪种方式,如果您创建了一个Pickler
用于多个酸洗操作的对象,则只需指定一次协议:
pickler = pickle.Pickler(output, -1)
pickler.dump(obj1)
pickler.dump(obj2)
etc...
注意:如果您正在运行不同版本的Python的环境中,则可能需要显式地使用(即,硬编码)它们都可以读取的特定协议编号(较新的版本通常可以读取较早版本产生的文件) 。
多个物件
虽然泡菜文件可以包含如上述样品中,当有这些数目不详的任何数量的腌制对象的,它往往更容易将其全部保存在某种可变大小的容器,就像一个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 input:
tech_companies = pickle.load(input)
主要优点是您无需知道要保存多少个对象实例即可在以后加载它们(尽管如果没有该信息就可以这样做,但它需要一些专门的代码)。请参阅相关问题的答案在腌制文件中保存和加载多个对象?有关执行此操作的不同方法的详细信息。我个人最喜欢@Lutz Prechelt的答案。它适用于此处的示例:
class Company:
def __init__(self, name, value):
self.name = name
self.value = value
def pickled_items(filename):
""" Unpickle a file of pickled data. """
with open(filename, "rb") as f:
while True:
try:
yield pickle.load(f)
except EOFError:
break
print('Companies in pickle file:')
for company in pickled_items('company_data.pkl'):
print(' name: {}, value: {}'.format(company.name, company.value))
回答 1
我认为,假设该对象是个,这是一个很强的假设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
做)。dill
腌制a 的原因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
。
回答 2
您可以使用anycache为您完成这项工作。它考虑了所有细节:
- 它使用莳萝作为后端,扩展了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)
Anycache首次调用myfunc
,并cachedir
使用唯一标识符(取决于函数名称及其参数)作为文件名将结果腌制到文件中。在任何连续运行中,将加载已腌制的对象。如果在cachedir
两次python运行之间保留了,则腌制的对象将从先前的python运行中获取。
有关更多详细信息,请参见文档
回答 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"))
但是,正如该答案指出的那样,泡菜经常失败。所以你应该真正使用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"))