如何检查一个对象是否可被pickle化

30

我有一个对象列表,其中包含不同类型的对象,我想对它们进行pickle。我只想pickle可pickle的对象。是否有一种标准方法可以检查对象是否为可pickle类型,而不是尝试pickle它?

文档说明如果发生pickling异常,则可能已经写入文件的某些字节后,尝试将对象pickle作为测试似乎不是一个好的解决方案。

我看到了这个帖子,但它没有回答我的问题。


尝试将其写入文件可能是一个解决方案。只需不要将其写入您的实际输出文件,而是写入其他地方。可以写入 /dev/null 或其他地方。 - Hyperboreus
以下是可被 Pickled 的规则: https://docs.python.org/3/library/pickle.html#what-can-be-pickled-and-unpickled - slushy
1
为什么你接受了鸭子类型的答案,而不是使用 dill.pickles(f) 提供的你所需要的功能呢? - Charlie Parker
我点赞了“鸭子类型”和“dill.pickles”的回答,因为它们提供了更多的细节和进一步阅读。但是,问题的提出者可能选择了它,因为“腌菜”答案是在“鸭子类型”答案之后两年才出现的。 - Chris Rudd
@charlie-parker 有时候你不想为每一个小功能都安装一个新的软件包。每个软件包意味着更多的依赖关系,更多的维护工作,以及更容易受到攻击的风险(软件包组件和仓库)。我更喜欢使用标准库提供的解决方案,而不是来源可疑的未知软件包。 - Ed_
3个回答

30

dill中有一个dill.pickles方法可以实现这一点。

>>> class Foo(object):
...   x = iter([1,2,3])
... 
>>> f = Foo()     
>>> 
>>> dill.pickles(f)
False

我们可以使用dill中的方法来查找故障原因。

>>> dill.detect.badtypes(f)
<class '__main__.Foo'>
>>> dill.detect.badtypes(f, depth=1)
{'__setattr__': <type 'method-wrapper'>, '__reduce_ex__': <type 'builtin_function_or_method'>, '__reduce__': <type 'builtin_function_or_method'>, '__str__': <type 'method-wrapper'>, '__format__': <type 'builtin_function_or_method'>, '__getattribute__': <type 'method-wrapper'>, '__class__': <type 'type'>, '__delattr__': <type 'method-wrapper'>, '__subclasshook__': <type 'builtin_function_or_method'>, '__repr__': <type 'method-wrapper'>, '__hash__': <type 'method-wrapper'>, 'x': <type 'listiterator'>, '__sizeof__': <type 'builtin_function_or_method'>, '__init__': <type 'method-wrapper'>}
>>> dill.detect.badtypes(f, depth=1).keys()
['__setattr__', '__reduce_ex__', '__reduce__', '__str__', '__format__', '__getattribute__', '__class__', '__delattr__', '__subclasshook__', '__repr__', '__hash__', 'x', '__sizeof__', '__init__']

所以,唯一失败的不是类的“内置”方法的东西是x...所以这是一个好的起点。让我们检查'x',如果它是问题,就用其他东西替换它。

>>> dill.pickles(Foo.x)
False
>>> Foo.x = xrange(1,4)
>>> dill.pickles(Foo.x)
True

是的,x导致了一个错误,并用 xrange 替换它可以正常工作,因为 dill 可以pickle xrange。还剩下什么要做吗?

>>> dill.detect.badtypes(f, depth=1).keys()
[]
>>> dill.detect.badtypes(f, depth=1)       
{}
>>> dill.pickles(f)                 
True
>>> 

显然(很可能是因为在类__dict__中引用了x,现在能够pickle),f现在也能pickle了...因此我们完成了。

dill还提供了trace来显示在pickling对象时的确切路径。

>>> dill.detect.trace(True)
>>> dill.pickles(f)
T2: <class '__main__.Foo'>
F2: <function _create_type at 0x10e79b668>
T1: <type 'type'>
F2: <function _load_type at 0x10e79b5f0>
T1: <type 'object'>
D2: <dict object at 0x10e7c6168>
Si: xrange(1, 4)
F2: <function _eval_repr at 0x10e79bcf8>
D2: <dict object at 0x10e7c6280>
True

当我尝试这个时,我得到了很多函数,以至于我更不确定从哪里开始(将一个类进行pickling,它似乎返回所有子方法)。 - Roelant
@Roelant:我猜当你说“尝试这个”时,你是指查看跟踪。Pickling 是递归的,因此您将看到许多“子对象”。每次看到像 F2D1 这样的标记时,都会打开另一个“子对象”以供检查,并且在实际 pickled 对象时有类似的关闭标记。 - Mike McKerns

7
我建议在这种情况下进行“鸭子测试”。尝试将其存储到临时文件或内存文件中,根据您找到的适当方法,如果失败则放弃结果,如果成功则重命名。
为什么?
在Python中,您可以通过两种方式检查对象是否具有某些属性。
检查对象是否是某个抽象基类的实例。例如,数字层次结构的根{{link1:抽象基类}}。如果您只想检查参数x是否为数字,而不关心其种类,请使用isinstance(x,Number)。
注意:请保留HTML标签。

或者先尝试它,然后处理异常。这在许多情况下发生。Pythonic哲学是基于鸭子的。 鸭子类型, 鸭子测试, 和 EAFP 是关键词。

我甚至相信,在社区的一部分的压力下,第一个已经在Python3中得到了适当的介绍,而许多人仍然坚信鸭子是使用Python的方法。

AFAIK没有特殊的预条件可以检查,在pickle的情况下也没有任何ABC可以检查对象是否符合要求。所以剩下的就是鸭子

也许还可以尝试其他方法,但可能不值得。手动检查对象以初步确定其是否适合进行腌制将非常困难。

谢谢,我熟悉Python的鸭子测试。只是让我惊讶的是没有更好的检查可pickle性的方法。每个可pickle对象都需要实现某些方法吗?我们不能只进行一种这些方法的鸭子测试吗? - Bitwise
当我第一次偶然发现它时,我感到困惑。我需要检查对象是否可迭代。我找到的最简单的方法是 try: mock = iter(data[0]) except TypeError:。这与某些 Python 的方式相当不同,因为理想情况下我应该将其视为可迭代并继续传递。然而,这种方法有一个严重的缺点,就是错误弹出的位置太低,很难找到它们。从我在文档中读到的内容来看,Python 使用其内部知识来 pickle 对象。这不像 __str__。你可以在奇怪的情况下提供一些帮助程序,但并不是每个地方都需要。我没有找到其他可靠的方法。 - luk32
这就解释了,谢谢。但在我看来,这仍然是一个奇怪的设计选择 - 但我可能没有看到所有的角度。 - Bitwise
@Bitwise Pickling 是一个复杂的,也许更重要的是递归过程,它严重依赖于作用域中的内容。请记住,Python 是一种动态语言,因此“什么在哪里”并不是一个容易回答的问题,实际上询问可能会改变答案。因此,实际上唯一确定对象是否可被 pickle 的方法是尝试进行 pickling。实际上,一个完美的 isPickleable() 可能需要解决停机问题。 - Schilcote

6

dill 可以比内置的 pickle 库更好地支持对象序列化。

我认为以下代码可以实现你的需求:

def is_picklable(obj):
  try:
    pickle.dumps(obj)

  except pickle.PicklingError:
    return False
  return True

除此之外,还没有官方API吗?错误处理可能会很慢/昂贵... - Charlie Parker
1
对于最近的Python版本,请使用except TypeError - Elazar
1
对于最近的Python版本,请使用except TypeError - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接