保存对象(数据持久性)

2024-03-29 09:22:02 发布

您现在位置:Python中文网/ 问答频道 /正文

我创建了这样一个对象:

company1.name = 'banana' 
company1.value = 40

我想保存这个对象。我该怎么做?


Tags: 对象namevaluebananacompany1
3条回答

你可以用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)

Anycache在第一次调用myfunc,并将结果pickle到 在cachedir中使用唯一标识符(取决于函数名及其参数)作为文件名的文件。 在任何连续的运行中,都会加载pickled对象。 如果在python运行之间保留了cachedir,则pickled对象取自上一次python运行。

有关更多详细信息,请参见documentation

您可以使用标准库中的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

实际上最好使用^{}模块,而不是pickle,因为前者是用C编写的,而且速度更快。它们之间有一些细微的差别,但是在大多数情况下它们是等价的,C版本将提供非常优异的性能。切换到它并不容易,只需将import语句更改为:

import cPickle as pickle

在Python 3中,cPickle被重命名为_pickle,但由于pickle模块现在自动看到What difference between pickle and _pickle in python 3?,因此不再需要这样做。

简而言之,您可以使用如下方法来确保代码在Python 2和3中都可用时始终使用C版本:

try:
    import cPickle as pickle
except ModuleNotFoundError:
    import pickle

数据流格式(协议)

pickle可以读写几种不同的、特定于Python的格式的文件,如documentation“Protocol version 0”中所述,称为protocols,因此是“人类可读的”。版本>;1是二进制的,可用的最高版本取决于所使用的Python版本。默认值也取决于Python版本。在Python 2中,默认的是协议版本0,但在Python 3.8.1中,默认的是协议版本4。在Python3.x中,模块中添加了一个pickle.DEFAULT_PROTOCOL,但Python2中不存在。

幸运的是,在每个调用中都有写pickle.HIGHEST_PROTOCOL的速记法(假设这是您想要的,而且您通常会这样做),只需使用文本号-1-类似于通过负索引引用序列的最后一个元素。 所以,不要写:

pickle.dump(obj, output, pickle.HIGHEST_PROTOCOL)

你可以写:

pickle.dump(obj, output, -1)

无论哪种方式,如果创建了用于多个pickle操作的Pickler对象,则只需指定一次协议:

pickler = pickle.Pickler(output, -1)
pickler.dump(obj1)
pickler.dump(obj2)
   etc...

注意:如果您所处的环境运行的是不同版本的Python,那么您可能需要显式地使用(即硬代码)一个特定的协议号,所有这些协议号都可以读取(较新版本通常可以读取较早版本生成的文件)。

多个对象

尽管pickle文件可以包含任意数量的pickle对象,如上面的示例所示,但当它们的数量未知时,通常更容易将它们存储在某种大小可变的容器中,如listtupledict,并在一次调用中将它们全部写入文件:

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)

主要优点是不需要知道保存了多少对象实例,以便以后加载它们(尽管这样做时不需要这些信息可能的,但需要一些稍微特殊的代码)。请参阅相关问题Saving and loading multiple objects in pickle file?的答案,以了解执行此操作的不同方法的详细信息。我个人喜欢@Lutz Prechelt的answer最好的。下面是它改编的例子:

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))

我认为假设对象是class是一个相当强的假设。如果不是class呢?还有一个假设是对象没有在解释器中定义。如果它是在解释器中定义的呢?另外,如果属性是动态添加的呢?当一些python对象在创建后将属性添加到它们的__dict__中时,pickle不考虑这些属性的添加(即它“忘记”添加了它们——因为pickle通过引用对象定义序列化)。

在所有这些情况下,picklecPickle都会让你非常失望。

如果您希望保存一个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。我们将引入另一个对象类型(alambda)以获得良好的度量。

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那样)。之所以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包,则可以使用git+https://github.com/uqfoundation/dill.git@master#egg=dill获得最新的dill。您可以使用pip install dill获得最新发布的版本。

相关问题 更多 >