PicklingError: Can’t pickle < class‘ numal. Decimal’> : 它不是与 decal.Decimal 相同的对象

这就是我今天在 href http://filmaster.com">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

任何帮助都将不胜感激。

78997 次浏览

Pickle 的一个奇怪之处在于,在对类的一个实例进行 Pickle 之前导入类的方式可能会微妙地更改 Pickle 对象。Pickle 要求您在对对象进行 Pickle 之前和取消 Pickle 之前导入的对象完全相同。

例如:

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

会(有时)做一个微妙的不同的目标:

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

尝试修改您的导入,它可能会纠正这个问题。

你是否以某种方式 reload(decimal),或者猴子修补十进制模块来改变十进制类?这是最有可能产生这种问题的两个因素。

我在运行木星笔记本时出现了这个错误。我认为问题在于我使用的是 %load_ext autoreload autoreload 2。重新启动内核并重新运行解决了这个问题。

通过调用 __init__来启动 multiprocessing进程可能会出现问题:

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

我也不能解释为什么会失败,但是我自己的解决方案是改变我所有的代码

from point import Point

import point

这个改变起作用了,我想知道为什么

我将用 Python 2.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>

我希望这能省点麻烦! 再见! !

我的问题是,我有一个同名的函数在一个文件中定义了两次。所以我猜它搞不清楚自己想泡哪个。

我也是

重启内核对我有用

我在调试时也有同样的问题(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

我的基本理解是,因为 star (*)我得到了这个 picklingError。

由于基于声誉的限制,我不能评论,但是 萨利姆 · 法赫迪的答案和调试路径让我找到了这个错误的原因,即使使用 dill而不是 pickle: 在底层,dill还可以访问 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()之前的某个函数) ,然后从 oemof.solph.models导入 Model并抛出一个错误,因为它的导入与 from oemof import solph.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)。

长话短说 : 在 pickle 对象时,确保从尽可能低的级别导入类。

附加 : 这似乎也适用于存储在您想 pickle 的 class-instance 属性中的对象。例如,如果 model有一个属性 es,它本身就是类 oemof.solph.energysystems.EnergySystem的一个对象,那么我们需要将它导入为:

from oemof.solph.energysystems import EnergySystem


es = EnergySystem()

我遇到了一个还没人提到的问题。我有一个包含 __init__文件的包,除了其他功能之外,它还可以:

from .mymodule import cls

然后我的顶级代码说:

import mypkg
obj = mypkg.cls()

这样做的问题是,在我的顶级代码中,类型看起来是 mypkg.cls,但实际上是 mypkg.mymodule.cls。使用完整路径:

obj = mypkg.mymodule.cls()

避免错误。

我在斯派德也犯过同样的错误。对我来说很简单。我在一个名为“ Class”的文件中定义了一个名为“ Class”的类。我将定义中的类的名称更改为“ Class _ obj”。pickle.dump(Class_obj,fileh)可以工作,但是当 pickle.dump(Class,fileh)保存在一个名为“ Class”的文件中时,它就不能工作了。

这个不可思议的函数解决了上面提到的错误,但是对我来说,它变成了另一个出人意料的错误“许可被拒绝”。不过,我想它可能会帮助某人找到一个解决方案,所以我还是发布了这个函数:

import tempfile
import time


from tensorflow.keras.models import save_model, Model


# Hotfix function
def make_keras_picklable():
def __getstate__(self):
model_str = ""
with tempfile.NamedTemporaryFile(suffix='.hdf5', delete=True) as fd:
save_model(self, fd.name, overwrite=True)
model_str = fd.read()
d = {'model_str': model_str}
return d


def __setstate__(self, state):
with tempfile.NamedTemporaryFile(suffix='.hdf5', delete=True) as fd:
fd.write(state['model_str'])
fd.flush()
model = load_model(fd.name)
self.__dict__ = model.__dict__




cls = Model
cls.__getstate__ = __getstate__
cls.__setstate__ = __setstate__


# Run the function
make_keras_picklable()


### create then save your model here ###