用Cython包装观察者模式



观察者模式在我的c++项目中频繁出现,现在我想通过Cython绑定将其公开给Python解释器。我试图构造一个最小的例子来说明这种情况。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?

这确实可以编译,但是当调用event()并通知观察者时出现分段错误:

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

这里的问题是什么?如何修复?

你的问题本质上是"用Python实现c++接口"。

唯一可移植的方法是编写一个实际的c++类

Cython有未记录的experimental_cpp_class_def选项允许使用Cython语法创建c++类。这并不漂亮(在我看来),但它有效

下面是如何实现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++异常重新抛出。(我认为在Cython语法中抛出c++异常是不可能的,但是你可以调用一个外部抛出帮助函数。)

如果你的观察者有多个方法,那么callback将是一个Python类,而不是直接调用它,你将调用它的方法,如(<object>this.callback).onEvent()

显然,ObserverImpl也可以直接在c++中编码。Py_INCREF, Py_DECREFPyObject_Call/PyObject_CallMethod是唯一需要的Python api。

最新更新