Я возился с Китоном, погружаясь глубже в киви, и я пытаюсь сделать свою собственную собственность Киви.
У меня есть следующие файлы с 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)
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, но это дало не результат
Я не знаю, почему вы хотели бы сделать что-то подобное, и я не уверен, что это разумно делать в долгосрочной перспективе. Но этот вопрос не лишен (по крайней мере теоретического) интереса.
Существует переопределение метода - одно и то же имя, одна и та же подпись, другая реализация - и это возможно в 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) невозможна.
def dispatch()
для файлов my.pyx и my.pxd, но проблема осталась, но я все равно получил сообщение"Error compiling ... Call with wrong number of arguments (expected 2, got 3)"
. Поэтому я попытался удалить строку, вызывающуюdispatch()
чтобы посмотреть, что происходит, и обнаружил, что my.pxd, похоже, не влияет на файл my.pyx, как если бы объявления там не импортировались. Я отредактирую свой вопрос на детали включения