【问题标题】:Saving an Object (Data persistence)保存对象(数据持久性)
【发布时间】:2011-05-30 14:41:04
【问题描述】:

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

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

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

【问题讨论】:

  • 请参阅example 了解如何使用泡菜的简单示例。
  • @MartinThoma:为什么你(似乎)更喜欢这个答案而不是接受的答案(linked question)?
  • 在我链接时,接受的答案没有protocol=pickle.HIGHEST_PROTOCOL。我的回答也提供了泡菜的替代品。

标签: python serialization save persistence pickle


【解决方案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 模块现在会自动执行此操作 - 请参阅 What difference between pickle and _pickle in python 3?

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

try:
    import cPickle as pickle
except ModuleNotFoundError:
    import pickle

数据流格式(协议)

pickle 可以读取和写入几种不同的、特定于 Python 的格式的文件,称为 protocols,如documentation 中所述,“协议版本 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 对象,您只需指定一次协议:

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)

主要优点是您不需要知道保存了多少对象实例以便稍后加载它们(尽管这样做可能没有这些信息可能的,它需要一些稍微专门的代码)。有关执行此操作的不同方法的详细信息,请参阅相关问题 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 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))

【讨论】:

  • 这对我来说很少见,因为我想会有一种更简单的方法来保存对象...类似 'saveobject(company1,c:\mypythonobjects)
  • @Peterstone:如果你只想存储一个对象,你只需要我示例中一半的代码——我故意按照我的方式编写它,以展示多个对象如何可以保存到同一个文件中(然后再读回)。
  • @Peterstone,职责分离有一个很好的理由。这样,酸洗过程中的数据的使用方式就没有限制。您可以将其存储到光盘中,也可以通过网络连接发送。
  • @martinaeau,这是为了回应 perstones 关于一个应该只有一个功能来将对象保存到磁盘的评论。 pickles 的职责是将对象转换为可以作为块处理的数据。将内容写入文件是文件对象的职责。通过将事物分开,可以实现更高的重用性,例如能够通过网络连接发送腌制数据或将其存储在数据库中,所有职责与实际数据分开对象转换
  • 您删除了company1company2。你为什么不也删除Company 并展示会发生什么?
【解决方案2】:

新版本的 pandas 还具有保存泡菜的功能。

我发现它更容易。例如

pd.to_pickle(object_to_save,'/temp/saved_pkl.pickle' )

【讨论】:

    【解决方案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 并将结果腌制到一个 cachedir 中的文件,使用唯一标识符(取决于函数名及其参数)作为文件名。 在任何连续运行中,都会加载腌制对象。 如果在 python 运行之间保留了cachedir,则腌制对象取自上一次 python 运行。

    如需了解更多详情,请参阅documentation

    【讨论】:

    • 如何使用anycache 保存多个实例,例如classlist 等容器(这不是调用函数的结果)?
    【解决方案4】:

    使用您的问题中的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"))
    

    然而,正如answer 所说,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"))
    

    【讨论】:

      【解决方案5】:

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

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

      如果你想保存一个object(任意创建),你有属性(在对象定义中添加,或者在之后添加)......你最好的选择是使用dill,它可以序列化几乎任何东西蟒蛇。

      我们从一堂课开始……

      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。我们将添加另一种对象类型(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 可以腌制 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获取最新发布的版本。

      【讨论】:

      • 当我尝试将dill(看起来很有希望)与包含音频文件的相当复杂的对象一起使用时,我收到了TypeError: __new__() takes at least 2 arguments (1 given)
      • @MikeiLL:当你做什么时,你会得到一个TypeError,究竟是什么?这通常是实例化类实例时参数数量错误的标志。如果这不属于上述问题的工作流程,您能否将其作为另一个问题发布,通过电子邮件提交给我,或者将其作为问题添加到dill github 页面上?
      • 对于任何关注的人,这是发布的related question @MikeLL -- 从答案来看,这显然不是dill 问题。
      • dill 给了我MemoryErrorcPicklepicklehickle 也是如此。
      • 在读取操作期间,我收到 dill RecursionError: maximum recursion depth exceeded 的以下错误,是否有可能克服这个问题?
      猜你喜欢
      • 2022-11-28
      • 2013-08-19
      • 2010-09-07
      • 2011-10-29
      • 2022-11-24
      • 2011-12-15
      相关资源
      最近更新 更多