Python “我可以”;pickle local objects“;如果我使用派生类?

Python “我可以”;pickle local objects“;如果我使用派生类?,python,python-3.x,nested,pickle,Python,Python 3.x,Nested,Pickle,pickle引用可以被pickle的对象集是相当有限的。事实上,我有一个函数返回一个动态生成的类,我发现我不能pickle该类的实例: >>> import pickle >>> def f(): ... class A: pass ... return A ... >>> LocalA = f() >>> la = LocalA() >>> with open('testing.pick

pickle
引用可以被pickle的对象集是相当有限的。事实上,我有一个函数返回一个动态生成的类,我发现我不能pickle该类的实例:

>>> import pickle
>>> def f():
...     class A: pass
...     return A
... 
>>> LocalA = f()
>>> la = LocalA()
>>> with open('testing.pickle', 'wb') as f:
...     pickle.dump(la, f, pickle.HIGHEST_PROTOCOL)
... 
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
AttributeError: Can't pickle local object 'f.<locals>.A'

这里发生了什么事?如果这很容易,为什么不使用此变通方法来实现允许对“本地对象”进行pickle的
转储
方法呢?

DerivedA
实例是可pickle的,因为
DerivedA
通过匹配其完全限定名的全局变量可用,这就是
pickle
在取消勾选时查找类的方式

尝试对本地类执行类似操作的问题在于,无法识别实例对应的
类。如果您运行两次
f
,您将得到两个
A
类,并且无法判断哪个类应该是来自另一次运行的程序的未勾选
A
实例类。如果你根本不运行
f
,你就不会得到
A
类,那么你对未点击实例的类型该怎么办?

我想你没有仔细阅读。参考文件还明确指出,只有以下对象是可pickle的:

  • 在模块顶层定义的函数(使用def,而不是>lambda)
  • 在模块顶层定义的内置函数
  • 在模块顶层定义的类
你的榜样

>>> def f():
...     class A: pass
...     return A
不在模块的顶层定义类,而是在
f()
范围内定义类<代码>pickle
适用于全局类,而不是本地类。这会自动使可酸洗测试失败

DerivedA
是一个全局类,所以一切都很好

至于为什么只有顶级(对您来说是全局的)类和函数不能被pickle,参考文献也回答了这个问题(粗体):

请注意,函数(内置和用户定义)是通过“完全限定”名称引用进行pickle的,而不是通过值进行pickle的。这意味着只有函数名与定义函数的模块名一起被pickle函数的代码及其任何函数属性都不会被pickle。因此,定义模块必须在取消勾选环境中可导入,并且该模块必须包含命名对象,否则将引发异常

类似地,类通过命名引用进行pickle,因此在取消pickle环境中应用相同的限制


好了<代码>pickle
仅按名称引用序列化对象,而不是按对象中包含的原始指令序列化对象。这是因为pickle的
工作是序列化对象层次结构,而不是别的。

我不同意,您可以同时pickle两者。您只需要使用更好的序列化程序,如
dill
dill
(默认情况下)通过保存类定义而不是通过引用pickle来pickle类,因此它不会使您的第一个案例失败。如果愿意,您甚至可以使用
dill
获取源代码

>>> import dill as pickle
>>> def f():
...   class A: pass
...   return A
... 
>>> localA = f()
>>> la = localA()
>>> 
>>> _la = pickle.dumps(la)
>>> la_ = pickle.loads(_la)
>>>    
>>> class DerivedA(localA): pass
... 
>>> da = DerivedA()
>>> _da = pickle.dumps(da)
>>> da_ = pickle.loads(_da)
>>> 
>>> print(pickle.source.getsource(la_.__class__))
  class A: pass

>>> 

您只能pickle在模块顶层定义的类的实例

但是,如果将本地定义的类升级到顶级,则可以对其实例进行pickle

您必须设置本地类的_; qualname __; class属性。然后必须将该类分配给同名的顶级变量

def define_class(name):
    class local_class:
        pass
    local_class.__qualname__ = name
    return local_class

class_A = define_class('class_A') # picklable
class_B = define_class('class_B') # picklable
class_X = define_class('class_Y') # unpicklable, names don't match

好的,只有类的名称被pickle。我认为类本身被保存了(不知何故),然后这意味着保存它的基类,这是不可pickle的。顺便说一句,如果我在一个环境中取消pickle它,其中有一个不相关的类,但具有相同的名称,我将得到这个不相关类的一些frankstein monster对象,但具有旧类的属性?我认为这可以归结为:pickle对象并不像我想象的那样独立。@fonini也来自引用:“……当类实例被pickle时,它们的类的代码和数据不会随之被pickle。只有实例数据被pickle。”所以,是的,这听起来是正确的行为。:)我完全超越了这一点。我打赌这是关于聚焦不同的事物,不一定更好?还是dill在速度和存储对象的大小方面也更好?但是谢谢你提到它
dill
并不是更快,而且在存储对象的大小方面也不是更好。事实上,在这两方面都比pickle更糟糕。就序列化各种类型的对象的能力而言,它要好得多,这就是我的意思。您的解决方案似乎是适合我的方式。您上面的示例对我很有用,但当我尝试实际代码时,我不断得到“Can't pickle:它与XX.YY不是同一个对象”。你知道我做错了什么吗?那是我的错。。。。我正在创建一个
class_A
的实例,然后在尝试pickle它之前重新定义这个类。
def define_class(name):
    class local_class:
        pass
    local_class.__qualname__ = name
    return local_class

class_A = define_class('class_A') # picklable
class_B = define_class('class_B') # picklable
class_X = define_class('class_Y') # unpicklable, names don't match