13

我有 2 个类,A 和 B。B 继承自 A。

//C++    
class A
{
    public:
        int getA() {return this->a;};
        A() {this->a = 42;}
    private:
        int a;

};

class B: public A
{
    public:
       B() {this->b = 111;};
       int getB() {return this->b;};
    private:
        int b;

};

现在我想使用 Cython 连接这两个类,并有可能从 B 实例调用 getA() 方法:

a = PyA()
b = PyB()
assert a.getA() == b.getA()

目前我的 pyx 文件如下所示:

cdef extern from "Inherit.h" :
    cdef cppclass A:
       int getA()

    cdef cppclass B(A):
       int getB()


cdef class PyA:
    cdef A* thisptr

    def __cinit__(self):
       print "in A: allocating thisptr"
       self.thisptr = new A()
    def __dealloc__(self):
       if self.thisptr:
           print "in A: deallocating thisptr"
           del self.thisptr

    def getA(self):
       return self.thisptr.getA()

cdef class PyB(PyA):
    def __cinit__(self):
       if self.thisptr:
          print "in B: deallocating old A"
          del self.thisptr
       print "in B: creating new b"
       self.thisptr = new B()

    def __dealloc__(self):
       if self.thisptr:
           print "in B: deallocating thisptr"
           del self.thisptr
           self.thisptr = <A*>0

    def getB(self):
       return (<B*>self.thisptr).getB()

虽然我希望这段代码不会做任何太危险的事情,但我也希望有更好的方法来处理它。

还使用该模块生成以下输出:

>>> from inherit import *
>>> b = PyB()
in A: allocating thisptr
in B: deallocating old A
in B: creating new b
>>> b.getA()
42
>>> b.getB()
111
>>> del b
in B: deallocating thisptr

而且我真的不喜欢分配一个 A 实例只是为了在之后立即释放它。

有关如何正确执行此操作的任何建议?

4

3 回答 3

8

我做了一些实验,并且已经准备好了答案,但现在我知道问题出在哪里:

如果你的扩展类型有基类型,__cinit__在你的方法被调用之前,会自动调用基类型的__cinit__方法;您不能显式调用继承的__cinit__方法。

所以真正的问题是 Cython 类型仍然没有构造函数,只有预初始化钩子__cinit__,它的行为更像默认构造函数。您不能从构造函数调用虚拟方法,也不能从__cinit__任何一个调用它(如果您进行调用,它的行为就像非虚拟一样)。

不知何故,里面返回__cinit__type(self)正确的类型对象,但它是没用的。Cython 没有静态字段,方法和类型对象只能是实例type(没有元类)。Python@staticmethod很容易被覆盖,所以它没用。

因此,没有其他方法可以将分配放入其中,并在您使用它的任何地方def __init__(self):检查是否已初始化。thisptr

您可以考虑创建一个全局虚拟 C++ 对象,并将其分配thisptr给以避免检查和崩溃。没有后初始化程序挂钩,因此您将无法检查是否已经进行了正确的初始化。

于 2012-05-09T23:34:41.410 回答
3

我以前从未关注过 Cython,所以如果这太离谱了,请原谅我。那说:

在 C++ 中,只要你有bar : foobar继承自foo),如果foo有一个默认构造函数,它会在bar创建时自动调用......除非你为父级调用你自己的自定义构造函数。

我不知道 Python,但一些快速的谷歌搜索告诉我,同样的原则也适用。即只有在不手动调用另一个PyA时才会调用默认构造函数。PyB

在那种情况下,这样的事情不会起作用吗?

cdef class PyA:
    cdef A* thisptr

    def __cinit__(self, bypasskey="")
       if bypasskey == "somesecret"
           print "in A: bypassing allocation"
       else
           print "in A: allocating thisptr"
           self.thisptr = new A()

...

cdef class PyB(PyA):
    def __cinit__(self):
       super( PyB, self ).__init__("somesecret")

请注意,我敢肯定它很粗糙。但也许这里的想法会给你一些工作?


这是另一个想法。我几乎可以肯定它会起作用(但语法已关闭),而且它肯定比上面的要干净得多:

cdef class PyA:
    cdef A* thisptr

    def __cinit__(self, t=type(A))
           self.thisptr = new t()

...

cdef class PyB(PyA):
    def __cinit__(self):
       super( PyB, self ).__init__(type(B))

或者它可能看起来像这样?

cdef class PyA:
    cdef A* thisptr

    def __cinit__(self, t=A)
           self.thisptr = new t()

...

cdef class PyB(PyA):
    def __cinit__(self):
       super( PyB, self ).__init__(B)

我不是为了赏金而发布这个(你也不会被迫将它分配给任何人),我只是与你分享一些想法。

我认为你可以/应该能够避免“崩溃解释器”,如果你要么

a)使第二个构造函数只对 b 可见(不知道这是否可能),或者

b) 在其他地方使用之前检查 a 是否为空,或者

c) 在绕过分配之前检查调用函数是否是 b 的构造函数。

此外,Cython C++ 文档清楚地表明,对于所有 C++ 改编,可能没有惯用的解决方案,带有模糊的、手摇的引号,例如“其他人(你?)可能需要进行一些试验才能找到最优雅的处理方式这个问题。”

于 2012-05-09T16:18:22.843 回答
1

(我对 Python 和 Cython 都是新手,所以请接受这个答案来了解它的价值。)如果您在__init__函数而不是__cinit__函数中初始化 thisptr,那么在这个特定示例中,事情似乎在没有额外分配/删除的情况下工作......基本上将上面的函数更改__cinit__为:

def __init__(self):
    print "in A: creating new A"
    self.thisptr = new A()

def __init__(self):
    print "in B: creating new B"
    self.thisptr = new B()

分别。但是,我确信这至少在理论上是不安全的(实际上也可能是不安全的),但也许有人可以评论到底有多不安全......

例如,从 Cython 的介绍论文中我们知道“__init__不能保证运行(例如,可以创建一个子类而忘记调用祖先构造函数)”。我无法构建发生这种情况的测试用例,但这可能是由于我普遍缺乏 Python 知识......

于 2012-05-23T20:59:17.967 回答