将pickle添加到用C++编写的Python扩展程序中

3
我已经用 C 写了一个 Python 扩展,它能正常工作且没有问题。现在我想要使用 pickle 库。但是从文档中我感到困惑。根据这里的建议,我编写了一个测试的 __reduce__ 函数,用于“扩展”:

http://docs.python.org/library/pickle.html#the-pickle-protocol

我不明白"可调用对象"应该是什么。我已经尝试使用PyObject_Type(self)的结果。这是“可调用”的并且基本上有效,但是当对象被反序列化时,它会调用__init__,这给我带来了一些麻烦。
是否有任何标准方法可以拥有一个只调用__new__方法的可调用对象,避免类初始化?

原则上,不应该调用 __init__,这是 pickle 的首选方式。请参阅 __getinitargs__ 下的内容:“当 unpickle 类实例时,通常不会调用其 __init __() 方法。”你正在实现 __getinitargs__ 吗? - Ricardo Cárdenes
好的,忘了它吧。那只适用于普通类。 - Ricardo Cárdenes
1个回答

2
如果您调用了__new__,并且使用type作为元类,则如果__new__的结果是您类型的子类型,则会调用__init__。换句话说,假设您有一个名为Foo的类。如果您调用PyObject_Type(self)的结果,则与调用Foo()相同。这意味着将调用Foo.__new__,如果返回值是Foo的子类型,则将调用__init__
深入挖掘一下,当您调用Foo()时,实际上是在调用type_call(在typeobject.c中),这是tp_new后跟tp_init的操作。如果您直接提供对象的新函数(例如Foo_new()而不是PyObject_Type(self)),则可以调用__new__而不调用__init__,从而获得您要查找的内容。(不要忘记为__new__提供Foo作为参数)。
因此,最终回答您的问题,您可以简单地调用Foo.__new__(Foo, ...)。以下是执行所需操作的代码。
class Foo(object):
    def __new__(cls):
        return super(Foo, cls).__new__(cls)

    def __init__(self):
        print "__init__"

    def __reduce__(self):
        return (Foo.__new__, (Foo, ))

print "one"
x = Foo()              # prints __init__

print "two"
y = Foo.__new__(Foo)   # does not print __init__

print "three"
import pickle
p = pickle.dumps(Foo)
z = pickle.loads(p)    # does not print __init__

作为一点儿旁注,当我试图理解所有这些时,我发现实际上在几乎所有情况下,我可以实现我的代码并允许__init__被调用。我的错误在于我没有将第三个参数中的东西分离出来。如果你只是提供了一堆直接更新__dict__的第三个参数内容(只要__init__接受它),而不提供第二个参数,那么完全没问题。如果你浏览cpython源代码中的模块和对象目录,你会看到许多__reduce__实现都是这样工作的。

嗨,Nathan,谢谢你的帮助,但出于某种原因,你在Python中描述的情况并没有发生在我编译的C++扩展类型中。特别是__reduce__输出中的第一项是: PyTuple_SetItem(res,0,PyObject_Type(self)); 正如我所说的,这将导致_pickle.loads(...)_,或类似的函数调用对象本身,并确实通过__new____init__。我已经找到了一个简单的解决方案,只需强制Pickle使用协议2即可。这不需要实现__reduce__函数,但需要__getnewargs__以及通常的__setstate____getstate__ - Guido Volpi
你需要使用self->tp_new来代替返回PyObject_Type(self),你试过了吗?如果可以的话,请告诉我。我会更新我的帖子。如果不行,我会找出我写错了什么。 - Nathan Binkert

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