【问题标题】:Is it possible to overload cpdef declaration on Cython .pxd?是否可以在 Cython .pxd 上重载 cpdef 声明?
【发布时间】:2018-06-18 19:07:02
【问题描述】:

我在玩 Cython,更深入地研究 kivy,我一直在尝试制作自己的 Kivy 财产。

我有以下文件定义 DocProperty: my.pyx:

from kivy.properties cimport Property, PropertyStorage
from kivy._event cimport EventDispatcher

cdef inline void observable_object_dispatch(object self, str name):
    cdef Property prop = self.prop
    prop.dispatch(self.obj, name)


class ObservableObject(object):

    # Internal class to observe changes inside a native python object.
    def __init__(self, *largs):
        self.prop = largs[0]
        self.obj = largs[1]
        super(ObservableObject, self).__init__()

    def __setattr__(self, name, value):
        object.__setattr__(self, name, value)
        observable_object_dispatch(self, name)


cdef class DocProperty(Property):

    def __init__(self, defaultvalue=None, rebind=False, **kw):
        self.baseclass = kw.get('baseclass', object)
        super(DocProperty, self).__init__(defaultvalue, **kw)
        self.rebind = rebind

    cpdef link(self, EventDispatcher obj, str name):
        Property.link(self, obj, name)
        cdef PropertyStorage ps = obj.__storage[self._name]
        ps.value = ObservableObject(self, obj, ps.value)

    cdef check(self, EventDispatcher obj, value):
        if Property.check(self, obj, value):
            return True
        if not isinstance(value, object):
            raise ValueError('{}.{} accept only object based on {}'.format(
            obj.__class__.__name__,
            self.name,
            self.baseclass.__name__))

    cpdef dispatch(self, EventDispatcher obj, str name):
        '''Dispatch the value change to all observers.
       .. versionchanged:: 1.1.0
           The method is now accessible from Python.
       This can be used to force the dispatch of the property, even if the
       value didn't change::
           button = Button()
           # get the Property class instance
           prop = button.property('text')
           # dispatch this property on the button instance
           prop.dispatch(button)
       '''
        cdef PropertyStorage ps = obj.__storage[self._name]
        ps.observers.dispatch(obj, ps.value, (name,), None, 0)

my.pxd:

from kivy.properties cimport Property, PropertyStorage
from kivy._event cimport EventDispatcher

cdef class DocProperty(Property):
    cdef object baseclass
    cdef public int rebind
    cpdef dispatch(self, EventDispatcher obj, str name)

快速试用一下:my.py

# -*- coding: utf-8 -*-

from kivy.event import EventDispatcher
import pyximport
pyximport.install()
from properties import DocProperty

if __name__ == '__main__':

    class ED(EventDispatcher):

        doc = DocProperty()

        def on_doc(self, obj, value):
            print 'printing doc', self.doc

    class DumbObj(object):

        def __init__(self, num):
            self._num = num

        @property
        def num(self):
            return 5

        @num.setter
        def num(self, value):
            self._num = value

    ed = ED()
    ed.doc = DumbObj(3)
    ed.doc.num = 4

当我运行 my.py 时,我在 DocProperty 的调度方法上得到一个“签名与先前的声明不兼容”,因为我尝试在 Property 上覆盖它的声明,以便它可以接受比原始代码声明更多的参数。是否可以重载在 pxd 上声明的 cpdef 方法?如果是这样,我做错了什么?

编辑: 在@ead's suggestion 之后,我尝试在dispatch 的声明中用普通的def 替换cpdef 语句,同时在两个文件上,一次只在其中一个文件上。但这没有任何效果。然后我尝试注释掉对 dispatch 的调用,看看如果它没有编译失败会发生什么。原来 DocProperty 的两个属性(基类和绑定)在赋值时都会引发 AttributeError。这很奇怪,因为这些是从 Kivy 源复制/粘贴的。这意味着 my.pxd 文件对我的 Cython 代码没有任何影响?我尝试从 cimporting my.pxd 到 my.pyx,但这也没有产生结果

【问题讨论】:

    标签: python overriding kivy cython


    【解决方案1】:

    我不知道您为什么要这样做,而且我不确定从长远来看这样做是否明智。但这个问题并非没有(至少理论上的)兴趣。

    method overriding - 相同的名称,相同的签名,不同的实现 - 这在 Cython 中是可能的,例如:

    %%cython
    cdef class A:
      cpdef get_number(self):
        return 0
    
    cdef class B(A):
      cpdef get_number(self):#overrides method
        return 1
    
    def print_number(A obj):
        print("My number is", obj.get_number()) #uses C-level dispatch when possible
    

    现在:

    >>> print_number(A())
    0
    >>> print_number(B())
    1
    

    这些代码行比表面上看到的要多:分派不是在 Python 中发生,而是在 C 级别发生 - 比 Python 的分派快得多,但灵活性稍差。

    还有function overloading - 相同的名称,不同的签名,两种方法都可以使用 - 这是not really possible in Python,因此在 Cython 中也没有。但是,与 C 不同的是,Python 允许更改同名方法的签名,以便使用最后一个签名:

    >>> class SomeClass:
         def do_something(self):
           pass
         def do_something(self, i):
           pass
    >>> SomeClass().do_something(7) #OK
    >>> SomeClass().do_something() #Error, argument is needed
    

    您可以使用 Cython 实现相同的行为,使用 def 而不是 cpdef 函数(但该函数不应再在 pxd 中声明,尽管您可以在父类中将其保留为 cpdef):

    %%cython
    ...
    class C(B):
      def get_number(self, m):
        return min(42,m)
    

    现在导致错误:

    >>> print_number(C())
    TypeError: get_number() missing 1 required positional argument: 'm'
    

    这次使用了 Python-dispatch(here 是关于它如何工作的更多信息)并且类层次结构中 get_number 的“最后”定义需要一个未提供的参数 - 因此出现错误。


    还有一件事:为什么不能更改函数的签名并保留为cpdef

    cpdef 函数有一个静态类型的部分。 A 类及其所有子类在其函数表中都有一个指向 get_number(self) 实现的指针,该指针的类型为:

    PyObject *(*get_number)(struct __pyx_obj_4test_A *, int __pyx_skip_dispatch);
    

    例如在A的表格中:

    struct __pyx_vtabstruct_4test_A {
      PyObject *(*get_number)(struct __pyx_obj_4test_A *, int __pyx_skip_dispatch);
    };
    

    get_number(self, int m) 对应的类型为:

    PyObject *(*get_number)(struct __pyx_obj_4test_B *, int, int __pyx_skip_dispatch);
    

    签名中有一个额外的int,因此它是不同的类型,不能写入__pyx_vtabstruct_4test_A.get_number - 这里 Cython 的行为类似于 C。

    另一方面,一个结构中不能有两个不同的成员具有相同的名称,因此不可能实现真正的重载(例如在 C++ 中的名称处理)。

    【讨论】:

    • 如果我理解正确,可以做我想做的(重载方法,而不是覆盖,谢谢指出)。所以我尝试在 my.pyx 和 my.pxd 文件上使用def dispatch() 声明,但问题仍然存在,我仍然得到"Error compiling ... Call with wrong number of arguments (expected 2, got 3)"。所以我尝试删除调用dispatch() 的行以查看会发生什么,发现 my.pxd 似乎对 my.pyx 文件没有影响,就好像那里的声明没有被导入一样。我将编辑关于包含详细信息的问题
    • @GugaFigueiredo Python 中没有真正的重载。您可以更改方法的签名,但不能使用旧方法。因此,如果您追求的是真正的超载,那么您将无法做到。
    • @GugaFigueiredo 在 pxd 中你只放了 cdef 和 cpdef 所以如果函数变成 def 它不应该再在 pxd 中。 3)“但问题仍然存在”是“发生了另一个问题”,这在您当前的示例中无法重现。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-08
    • 2021-11-08
    • 2021-02-28
    • 2021-08-24
    • 1970-01-01
    相关资源
    最近更新 更多