腌制错误:无法将<class 'decimal.Decimal'>腌制起来:它不是decimal.Decimal相同的对象。

85
今天我在filmaster.com上遇到了以下错误信息:
PicklingError: Can't pickle <class
'decimal.Decimal'>: it's not the same
object as decimal.Decimal
这句话的确有些难以理解...它似乎与Django缓存有关。您可以在此处查看完整的回溯信息:
Traceback (most recent call last):

 File
"/home/filmaster/django-trunk/django/core/handlers/base.py",
line 92, in get_response    response =
callback(request, *callback_args,
**callback_kwargs)

 File
"/home/filmaster/film20/film20/core/film_views.py",
line 193, in show_film   
workflow.set_data_for_authenticated_user()

 File
"/home/filmaster/film20/film20/core/film_views.py",
line 518, in
set_data_for_authenticated_user   
object_id = self.the_film.parent.id)

 File
"/home/filmaster/film20/film20/core/film_helper.py",
line 179, in get_others_ratings   
set_cache(CACHE_OTHERS_RATINGS,
str(object_id) + "_" + str(user_id),
userratings)

 File
"/home/filmaster/film20/film20/utils/cache_helper.py",
line 80, in set_cache    return
cache.set(CACHE_MIDDLEWARE_KEY_PREFIX
+ full_path, result, get_time(cache_string))

 File
"/home/filmaster/django-trunk/django/core/cache/backends/memcached.py",
line 37, in set   
self._cache.set(smart_str(key), value,
timeout or self.default_timeout)

 File
"/usr/lib/python2.5/site-packages/cmemcache.py",
line 128, in set    val, flags =
self._convert(val)

 File
"/usr/lib/python2.5/site-packages/cmemcache.py",
line 112, in _convert    val =
pickle.dumps(val, 2)

PicklingError: Can't pickle <class
'decimal.Decimal'>: it's not the same
object as decimal.Decimal
Filmaster的源代码可以从这里下载:bitbucket.org/filmaster/filmaster-test。非常感谢您提供的任何帮助。

我在编写一个错误的__getstate__方法来更改对象的pickle行为后,遇到了类似的错误。不确定问题是什么,但请检查是否存在这些问题。 - partofthething
2
我也看到过这种情况,特别是在类装饰器中,具体来说是six.add_metaclass。 - dbn
14个回答

116

在运行jupyter notebook时,我遇到了这个错误。我认为问题是由于我使用了%load_ext autoreloadautoreload 2所导致的。重新启动内核并重新运行解决了问题。


10
似乎修改类方法是问题的原因。我猜测autoreload没有更新保存在其他地方的定义。重新启动可以解决这个问题,因为新的定义会加载到两个位置。 - typesanitizer
4
有没有其他解决方案,可以在不重启内核的情况下应对这种场景(这样做有点违背“自动重新加载”扩展的初衷)? - stav
@Stav,这确实会打破“autoreload”的目的,即重新加载模块而不删除与该模块相关联的在内存中加载的值。 - Jivan
没想到在这里看到这个,更没想到它竟然是解决方案。但事实就是如此。谢谢! - Mattkwish

39

Pickle的一个奇怪之处在于,在您对其实例进行pickle之前导入类的方式可能会微妙地改变pickled对象。Pickle要求您在pickle和unpickle之前,必须完全相同地导入对象。

所以例如:

from a.b import c
C = c()
pickler.dump(C)

将有时会创建与以下对象微妙不同的对象:

from a import b
C = b.c()
pickler.dump(C)

尝试调整您的导入方式,这可能会纠正问题。


22
为什么这个腌制问题只在成千上万次的请求中出现一次,而通常情况下都能正常工作呢? - michuk

29

我将演示在Python2.7中使用简单Python类时遇到的问题:

In [13]: class A: pass  
In [14]: class B: pass

In [15]: A
Out[15]: <class __main__.A at 0x7f4089235738>

In [16]: B
Out[16]: <class __main__.B at 0x7f408939eb48>

In [17]: A.__name__ = "B"

In [18]: pickle.dumps(A)
---------------------------------------------------------------------------
PicklingError: Can't pickle <class __main__.B at 0x7f4089235738>: it's not the same object as __main__.B

这个错误是因为我们试图转储A,但由于我们将其名称更改为引用另一个对象“B”,pickle实际上对要转储的对象 - A类或B类 - 感到困惑。显然,pickle的开发人员非常聪明,已经对此行为进行了检查。

解决方法: 检查您尝试转储的对象是否与另一个对象具有冲突的名称。

我已经使用ipython和ipdb演示了上述情况的调试:

PicklingError: Can't pickle <class __main__.B at 0x7f4089235738>: it's not the same object as __main__.B

In [19]: debug
> /<path to pickle dir>/pickle.py(789)save_global()
    787                 raise PicklingError(
    788                     "Can't pickle %r: it's not the same object as %s.%s" %
--> 789                     (obj, module, name))
    790
    791         if self.proto >= 2:

ipdb> pp (obj, module, name)               **<------------- you are trying to dump obj which is class A from the pickle.dumps(A) call.**
(<class __main__.B at 0x7f4089235738>, '__main__', 'B')
ipdb> getattr(sys.modules[module], name)   **<------------- this is the conflicting definition in the module (__main__ here) with same name ('B' here).**
<class __main__.B at 0x7f408939eb48>

我希望这能解决一些麻烦!再见!!


13

我也无法解释为什么这个出错了,但是我自己的解决方法是更改所有代码,不再执行

from point import Point

import point

这一次更改就可以了,我很想知道为什么......希望对你有帮助。


3
这也帮助了我,我很想知道为什么! - sanchitarora
有关它为什么有效的任何更新?我猜想import XX重新加载所有内容,而从XX导入XXX只重新加载特定的模块或函数。 - Albert Chen

8

使用multiprocessing调用__init__启动进程可能会出现问题。以下是一个演示:

import multiprocessing as mp

class SubProcClass:
    def __init__(self, pipe, startloop=False):
        self.pipe = pipe
        if startloop:
            self.do_loop()

    def do_loop(self):
        while True:
            req = self.pipe.recv()
            self.pipe.send(req * req)

class ProcessInitTest:
    def __init__(self, spawn=False):
        if spawn:
            mp.set_start_method('spawn')
        (self.msg_pipe_child, self.msg_pipe_parent) = mp.Pipe(duplex=True)

    def start_process(self):
        subproc = SubProcClass(self.msg_pipe_child)
        self.trig_proc = mp.Process(target=subproc.do_loop, args=())
        self.trig_proc.daemon = True
        self.trig_proc.start()

    def start_process_fail(self):
        self.trig_proc = mp.Process(target=SubProcClass.__init__, args=(self.msg_pipe_child,))
        self.trig_proc.daemon = True
        self.trig_proc.start()

    def do_square(self, num):
        # Note: this is an synchronous usage of mp,
        # which doesn't make sense. But this is just for demo
        self.msg_pipe_parent.send(num)
        msg = self.msg_pipe_parent.recv()
        print('{}^2 = {}'.format(num, msg))

使用以上代码,如果我们运行它:

if __name__ == '__main__':
    t = ProcessInitTest(spawn=True)
    t.start_process_fail()
    for i in range(1000):
        t.do_square(i)

我们遇到了这个错误:
Traceback (most recent call last):
  File "start_class_process1.py", line 40, in <module>
    t.start_process_fail()
  File "start_class_process1.py", line 29, in start_process_fail
    self.trig_proc.start()
  File "/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/multiprocessing/process.py", line 105, in start
    self._popen = self._Popen(self)
  File "/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/multiprocessing/context.py", line 212, in _Popen
    return _default_context.get_context().Process._Popen(process_obj)
  File "/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/multiprocessing/context.py", line 274, in _Popen
    return Popen(process_obj)
  File "/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/multiprocessing/popen_spawn_posix.py", line 33, in __init__
    super().__init__(process_obj)
  File "/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/multiprocessing/popen_fork.py", line 21, in __init__
    self._launch(process_obj)
  File "/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/multiprocessing/popen_spawn_posix.py", line 48, in _launch
    reduction.dump(process_obj, fp)
  File "/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/multiprocessing/reduction.py", line 59, in dump
    ForkingPickler(file, protocol).dump(obj)
_pickle.PicklingError: Can't pickle <function SubProcClass.__init__ at 0x10073e510>: it's not the same object as __main__.__init__

如果我们改用fork而不是spawn

if __name__ == '__main__':
    t = ProcessInitTest(spawn=False)
    t.start_process_fail()
    for i in range(1000):
        t.do_square(i)

我们遇到了这个错误:
Process Process-1:
Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/multiprocessing/process.py", line 254, in _bootstrap
    self.run()
  File "/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/multiprocessing/process.py", line 93, in run
    self._target(*self._args, **self._kwargs)
TypeError: __init__() missing 1 required positional argument: 'pipe'

但如果我们调用start_process方法,在mp.Process的目标中不调用__init__,就像这样:

if __name__ == '__main__':
    t = ProcessInitTest(spawn=False)
    t.start_process()
    for i in range(1000):
        t.do_square(i)

无论我们使用 spawn 还是 fork,它都能按预期工作。


8

你是否某种方式重新加载了decimal,或者猴子补丁了decimal模块以更改Decimal类?这些是最有可能产生此类问题的两个因素。


3

我也遇到了同样的问题

重启内核对我有用


是的,重启内核对我也解决了问题。谢谢你提到 :) - toom

2
由于基于声誉的限制,我无法发表评论,但Salim Fahedy的答案和遵循调试路径使我确定了这个错误的原因,即使使用dill而不是pickledill在底层也访问了一些dill函数。在pickle._Pickler.save_global()中有一个import发生。对我来说,这似乎更像是一个“黑客”而不是真正的解决方案,因为一旦您尝试pickle的实例的类没有从包中类的最低级别导入,此方法将失败。对于糟糕的解释我感到抱歉,也许示例更合适:

以下示例将失败:

from oemof import solph

...
(some code here, giving you the object 'es')
...

model = solph.Model(es)
pickle.dump(model, open('file.pickle', 'wb))

它失败了,因为您虽然可以使用solph.Model,但实际上类为oemof.solph.models.Model。调用save_global()(或者在此之前的某个函数将其传递给save_global())解决了该问题,但是由于它不是与from oemof import solph.Model相同的导入,所以它从oemof.solph.models导入Model并抛出错误(或类似这样的东西,我对工作原理不是100%确定)。
以下示例可行:
from oemof.solph.models import Model

...
some code here, giving you the object 'es')
...

model = Model(es)
pickle.dump(model, open('file.pickle', 'wb'))

这段代码之所以可行,是因为现在 Model 对象是从同一位置导入的, pickle._Pickler.save_global() 导入了比较对象(obj2)。

长话短说:当对一个对象进行 pickling 的时候,请确保从最低可能的级别导入类。

补充:这似乎也适用于存储在要 pickle 的类实例属性中的对象。例如,如果 model 有一个属性 es ,它本身是< code>oemof.solph.energysystems.EnergySystem类的一个对象,我们需要将其导入为:

from oemof.solph.energysystems import EnergySystem

es = EnergySystem()

1
很棒的答案,解释得非常清楚。 - Tom McLean

1
我的问题是在一个文件中有两个同名的函数定义。所以我猜它不知道要pickle哪一个。

1

我在调试(Spyder)时遇到了同样的问题。如果运行程序,一切正常。但是,如果我开始调试,我就会遇到picklingError。

但是,一旦我选择了在每个文件的运行配置中选择在专用控制台中执行选项(快捷键:ctrl+F6),一切都像预期的那样正常工作。我不知道它是如何适应的。

注意:在我的脚本中,我有许多导入,例如

from PyQt5.QtWidgets import *
from PyQt5.Qt import *
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import os, sys, re, math

我的基本理解是,因为星号 (*) 的存在,我才会遇到该PicklingError错误。


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