Обтекание шаблона наблюдателя с помощью Cython

Шаблон наблюдателя часто появляется в моем проекте C ++, который я сейчас хочу представить интерпретатору Python через привязки Cython. Я попытался построить минимальный пример, иллюстрирующий ситуацию. Spectacle принимает любой объект, полученный из абстрактного базового класса Observerтакой как Onlooker, Когда мы звоним Spectacle::event()Каждый зарегистрированный наблюдатель уведомляется.

Это содержимое файла ObserverPattern.h:

class Spectacle {
private:
std::vector<Observer*> observers;
public:

Spectacle() {};
virtual ~Spectacle() {};

virtual void registerObserver(Observer* observer) {
this->observers.push_back(observer);
}

virtual void event() {
std::cout << "event triggered" << std::endl;
for (Observer* observer : this->observers) {
observer->onEvent();
}
}
};

class Observer {
public:
Observer() {};
virtual ~Observer() {};
virtual void onEvent() = 0;

};

class Onlooker : public Observer {
public:
Onlooker() {};
virtual ~Onlooker() {};
virtual void onEvent() {
std::cout << "event observed" << std::endl;
}
};

И это содержание моего .pyx файл, содержащий привязки:

    cdef extern from "ObserverPattern.h":
cdef cppclass _Spectacle "Spectacle":
_Spectacle() except +
void registerObserver(_Observer* observer)
void event()

cdef extern from "ObserverPattern.h":
cdef cppclass _Observer "Observer":
_Observer() except +
void onEvent()

cdef extern from "ObserverPattern.h":
cdef cppclass _Onlooker "Onlooker":
_Onlooker() except +
void onEvent()

cdef class Spectacle:
cdef _Spectacle _this

def event(self):
self._this.event()

def registerObserver(self, Observer observer):
self._this.registerObserver(observer._this)

cdef class Observer:
cdef _Observer* _this   # must be a pointer because _Observer has pure virtual method

cdef class Onlooker(Observer):
pass   # what should be the class body?

Это компилируется, но segfaults, когда event() вызывается и наблюдатели уведомляются:

>>> spec = CythonMinimal.Spectacle()
>>> look = CythonMinimal.Onlooker()
>>> spec.registerObserver(look)
>>> spec.event()
event triggered
Segmentation fault: 11

В чем здесь проблема и как может выглядеть исправление?

0

Решение

Ваша проблема по сути «реализовать интерфейс C ++ в Python».

Единственный переносимый способ сделать это — написать реальный класс C ++
это перезвонит в Python.

Cython недокументирован experimental_cpp_class_def опция, которая позволяет
создавать классы C ++ с использованием синтаксиса Cython. Это не красиво (ИМО), но работает
для многих сценариев.

Вот как вы могли бы реализовать Observer что делегаты на предоставленные
Вызываемый Python:

from cpython.ref cimport PyObject, Py_INCREF, Py_DECREF

cdef cppclass ObserverImpl(_Observer):
PyObject* callback

__init__(object callback):  # constructor. "this" argument is implicit.
Py_INCREF(callback)
this.callback = <PyObject*>callback

__dealloc__():  # destructor
Py_DECREF(<object>this.callback)

void onEvent():
(<object>this.callback)()  # exceptions will be ignored

И вот как вы могли бы использовать это:

def registerObserver(self, callback not None):  # user passes any Python callable
self._this.registerObserver(new ObserverImpl(callback))

Объекты C ++, как и структуры C, не могут содержать управляемые Cython object
Рекомендации. Вот почему вы должны использовать PyObject* поле и управлять ссылкой
считая себя Внутри методов вы, конечно, можете использовать любую функцию Cython.

Еще один сложный момент — распространение исключений. onEvent() Метод, определенный в C ++, не может распространять исключения Python. Cython будет просто игнорировать исключения, которые он не может распространять. Если вы хотите добиться большего успеха, поймайте их сами и сохраните где-нибудь для последующей проверки или перезапустите как исключение C ++. (Я думаю, что невозможно генерировать исключения C ++ в синтаксисе Cython, но вы можете вызвать внешнюю вспомогательную функцию throwing.)

Если у вашего наблюдателя есть более одного метода, то callback будет классом Python, и вместо того, чтобы вызывать его напрямую, вы будете вызывать его методы, такие как (<object>this.callback).onEvent(),

Очевидно, что ObserverImpl также может быть закодирован непосредственно в C ++. Py_INCREF, Py_DECREF а также PyObject_Call/PyObject_CallMethod являются единственными необходимыми API Python.

3

Другие решения

Других решений пока нет …

По вопросам рекламы ammmcru@yandex.ru
Adblock
detector