Можно ли перегрузить объявление cpdef на Cython .pxd?

1

Я возился с Китоном, погружаясь глубже в киви, и я пытаюсь сделать свою собственную собственность Киви.

У меня есть следующие файлы с 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, чтобы он мог принять один аргумент больше, чем оригинальное объявление кода. Возможно ли даже перегрузить методы cpdef, объявленные на pxd? Если да, то что я делаю неправильно?

Edit: После @ead внушения, я попытался заменить cpdef заявления с простым def на декларациях dispatch, на обоих файлов и только один из них в то время. Но это не имело никакого эффекта. Затем я попытался прокомментировать вызов для отправки, чтобы узнать, что происходит, если он не поддается компиляции. Оказывается, оба атрибута DocProperty (базовый класс и привязка) повышают AttributeError при назначении. Что странно, потому что они были скопированы/вставлены из источника Kivy. Это означает, что файл my.pxd не влияет на мой код Cython? Я попытался из cimporting my.pxd в my.pyx, но это дало не результат

Теги:
kivy
cython
override

1 ответ

0

Я не знаю, почему вы хотели бы сделать что-то подобное, и я не уверен, что это разумно делать в долгосрочной перспективе. Но этот вопрос не лишен (по крайней мере теоретического) интереса.

Существует переопределение метода - одно и то же имя, одна и та же подпись, другая реализация - и это возможно в 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.

Существует также перегрузка функций - одно и то же имя, разные подписи, оба метода могут быть использованы - это на самом деле невозможно в 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-отправка (вот немного больше информации о том, как она работает), а "последнее" определение 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++ с ее namemangling) невозможна.

  • 0
    Если я правильно понимаю, можно делать то, что я хочу (перегрузка метода, а не переопределение, спасибо за указание на это). Поэтому я попытался использовать объявление def dispatch() для файлов my.pyx и my.pxd, но проблема осталась, но я все равно получил сообщение "Error compiling ... Call with wrong number of arguments (expected 2, got 3)" . Поэтому я попытался удалить строку, вызывающую dispatch() чтобы посмотреть, что происходит, и обнаружил, что my.pxd, похоже, не влияет на файл my.pyx, как если бы объявления там не импортировались. Я отредактирую свой вопрос на детали включения
  • 0
    @GugaFigueiredo В Python нет реальной перегрузки. Вы можете изменить сигнатуру метода, но не можете использовать старую. Итак, если вы действительно перегружены, тогда вы не сможете этого сделать.
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню