保存对象(数据持久性)

新手上路,请多包涵

我创建了一个像这样的对象:

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

我想保存这个对象。我怎样才能做到这一点?

原文由 Peterstone 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 582
2 个回答

您可以使用标准库中的 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 模块现在自动执行它 - 看看 pickle 和 _pickle 之间有什么区别在 python 3 中? .

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

 try:
    import cPickle as pickle
except ModuleNotFoundError:
    import pickle

数据流格式(协议)

pickle 可以读取和写入几种不同的、特定于 Python 的格式的文件,称为 文档 中描述的 _协议_,“协议版本 0”是 ASCII,因此是“人类可读的”。版本 > 0 是二进制的,可用的最高版本取决于使用的 Python 版本。默认值还取决于 Python 版本。在 Python 2 中,默认值为 Protocol version 0 ,但在 Python 3.8.1 中,它是 Protocol version 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 用于多个泡菜操作的对象,则只需指定一次协议:

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

_注意_:如果您在运行不同版本 Python 的环境中,那么您可能希望显式使用(即硬编码)所有它们都可以读取的特定协议号(更高版本通常可以读取由早期版本生成的文件) .

多个对象

虽然泡菜文件 可以 包含任意数量的泡菜对象,如上面的示例所示,但当它们的数量未知时,将它们全部存储在某种可变大小的容器中通常更容易,例如 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 inp:
    tech_companies = pickle.load(inp)

主要优点是您不需要知道保存了多少对象实例以便稍后加载它们(尽管在没有这些信息的情况下这样做 可能的,但它需要一些稍微专门的代码)。查看相关问题的答案 在泡菜文件中保存和加载多个对象? 有关执行此操作的不同方法的详细信息。就我个人而言,我最喜欢@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))

原文由 martineau 发布,翻译遵循 CC BY-SA 4.0 许可协议

我认为假设对象是 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 。我们将放入另一种对象类型 (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
>>>

有用。 The reason pickle fails, and dill doesn’t, is that dill treats __main__ like a module (for the most part),并且还可以腌制类定义而不是通过引用腌制(就像 pickle 一样)。 dill 可以 pickle 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 包,您可以获得最新的 dillgit+https://github.com/uqfoundation/dill.git@master#egg=dill 。您可以使用 pip install dill 获取最新发布的版本。

原文由 Mike McKerns 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题