问题 Cython和C ++继承


我有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实例只是为了立即释放它。

关于如何正确地做到这一点的任何建议?


4009
2018-04-24 13:00


起源



答案:


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

如果您的扩展类型具有基本类型,则 __cinit__ 的方法   在您之前自动调用基类型 __cinit__ 方法是   所谓的;你不能显式调用继承 __cinit__ 方法。

所以真正的问题是Cython类型仍然没有构造函数,只有pre initializer hook __cinit__ 其行为更像默认构造函数。您无法从构造函数中调用虚方法,也无法从中调用它 __cinit__ 要么(如果你打电话,它表现得像非虚拟)。

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

所以没有其他方法可以将分配放在里面 def __init__(self):,并检查初始化 thisptr 无论你在哪里使用它。

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


7
2018-05-09 23:34





我之前从未看过Cython,所以请原谅我,如果这样的话。那说:

在C ++中,只要你有 bar : foo (bar 继承自 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 ++的改编,用模糊的,手工波浪的引用,例如“可能需要其他人(你?)进行一些试验才能找到处理这个问题的最优雅的方法。”


3
2018-05-09 16:18



好吧,通过打开赏金,我正在为这个案子寻找一个惯用的构造。你说你不懂Python和Cython。虽然您的答案可以适用于合法的Python(和Cython)代码,但这会让Python用户有权使解释器崩溃,这比浪费内存分配更糟糕。 - ascobol
我回复你的评论太长了,请看我帖子的后半部分。 - Mahmoud Al-Qudsi
a)我认为Cython中只能有一个构造函数,就像在Python中一样。 b)然后在每种方法中我们都需要检查正确的初始化... c)这可能是C模块中任意预定义的值。在PyA __ cinit__中,我们将使用这个非平凡值来检查另一个参数,该值将绕过分配。在这种情况下,用户不能“意外地”使解释器崩溃。 - ascobol
@ascobol你是对的 - 我没想过Python可能没有多个构造函数。我已经改变了我原来的答案只有一个构造函数(并且它有点/更安全)以及提供了一个可能实际上是惯用解决方案的新答案。 - Mahmoud Al-Qudsi
不幸的是c)似乎不可行,因为PyA'构造函数'在PyB构造函数之前被自动调用(参见我的问题中的输出),所以我们不能从cython中明确地调用它。你的另一个想法既不能用于同样的原因,也因为cinit参数必须是python类型(纯python类型或cython创建的类型)而不是C ++类型。也许Cython缺少一些功能? - ascobol


答案:


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

如果您的扩展类型具有基本类型,则 __cinit__ 的方法   在您之前自动调用基类型 __cinit__ 方法是   所谓的;你不能显式调用继承 __cinit__ 方法。

所以真正的问题是Cython类型仍然没有构造函数,只有pre initializer hook __cinit__ 其行为更像默认构造函数。您无法从构造函数中调用虚方法,也无法从中调用它 __cinit__ 要么(如果你打电话,它表现得像非虚拟)。

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

所以没有其他方法可以将分配放在里面 def __init__(self):,并检查初始化 thisptr 无论你在哪里使用它。

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


7
2018-05-09 23:34





我之前从未看过Cython,所以请原谅我,如果这样的话。那说:

在C ++中,只要你有 bar : foo (bar 继承自 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 ++的改编,用模糊的,手工波浪的引用,例如“可能需要其他人(你?)进行一些试验才能找到处理这个问题的最优雅的方法。”


3
2018-05-09 16:18



好吧,通过打开赏金,我正在为这个案子寻找一个惯用的构造。你说你不懂Python和Cython。虽然您的答案可以适用于合法的Python(和Cython)代码,但这会让Python用户有权使解释器崩溃,这比浪费内存分配更糟糕。 - ascobol
我回复你的评论太长了,请看我帖子的后半部分。 - Mahmoud Al-Qudsi
a)我认为Cython中只能有一个构造函数,就像在Python中一样。 b)然后在每种方法中我们都需要检查正确的初始化... c)这可能是C模块中任意预定义的值。在PyA __ cinit__中,我们将使用这个非平凡值来检查另一个参数,该值将绕过分配。在这种情况下,用户不能“意外地”使解释器崩溃。 - ascobol
@ascobol你是对的 - 我没想过Python可能没有多个构造函数。我已经改变了我原来的答案只有一个构造函数(并且它有点/更安全)以及提供了一个可能实际上是惯用解决方案的新答案。 - Mahmoud Al-Qudsi
不幸的是c)似乎不可行,因为PyA'构造函数'在PyB构造函数之前被自动调用(参见我的问题中的输出),所以我们不能从cython中明确地调用它。你的另一个想法既不能用于同样的原因,也因为cinit参数必须是python类型(纯python类型或cython创建的类型)而不是C ++类型。也许Cython缺少一些功能? - ascobol


(我是Python和Cython的新手,所以请将这个答案用于它的价值。)如果你初始化thisptr in __init__ 功能而不是 __cinit__ 功能,事情似乎在这个特定的例子中工作,没有额外的分配/删除...基本上改变你的 __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知识......


1
2018-05-23 20:59