2013-06-12 6 views
0

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

Это содержимое файла 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? 

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

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

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

+0

Создайте класс наблюдателя cdef так же, как вы сделали с Spectacle, а затем «def registerObserver (self, Observer observer): self._this.registerObserver (& observer._this) '. –

+0

@CzarekTomczak Я расширил этот пример и включил ваш совет, но все еще есть проблема. Обратите внимание: 'observer._this' не может быть экземпляром' _Observer', потому что '_Observer' является абстрактным (имеет чистый виртуальный метод). Я изменил его на '_Observer *', чтобы он скомпилировался. – clstaudt

ответ

3

Ваша проблема заключается в «внедрении интерфейса C++ в Python».

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

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

Вот как можно реализовать 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. Китон просто игнорирует исключения, которые он не может распространять. Если вы хотите сделать лучше, поймайте их сами и сохраните где-нибудь для последующего осмотра или реконструирования как исключение C++. (Я думаю, что невозможно исключить исключения C++ в синтаксисе Cython, но вы можете вызвать вспомогательную вспомогательную функцию throwing.)

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

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

+0

Спасибо за разъяснение этого. Как я боялся, это совсем не тривиально, но я попробую это. – clstaudt

+0

@Nikita Nemkin: is void onEvent() кроме *: 'правильный синтаксис для распространения исключений? Или как это сделать? –

+0

'except' clauses only tell _Cython_ как сигнализировать и проверять исключения. 'onEvent' вызывается из _C++ _, а C++ ничего не знает о системе исключений Python. Нет простого решения для распространения исключений Python в сторонних кадрах стека. Я уже обрисовал обходные пути: 1) игнорировать исключения обратного вызова; 2) хранить их и проверять позже; 3) копировать в стороннюю систему отчетов об ошибках (например, исключения на C++). –

Смежные вопросы