0

观察者模式经常出现在我的 C++ 项目中,我现在想通过 Cython 绑定将其公开给 Python 解释器。我试图构建一个最小的例子来说明这种情况。ASpectacle接受从抽象基类派生的任何对象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

这里有什么问题,修复如何?

4

1 回答 1

3

您的问题本质上是“在 Python 中实现 C++ 接口”。

唯一可移植的方法是编写一个实际的 C++ 类,该类将回调 Python。

Cython 具有未记录experimental_cpp_class_def的选项,允许使用 Cython 语法创建 C++ 类。它并不漂亮(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 异常。Cython 将简单地忽略它无法传播的异常。如果您想做得更好,请自己捕获它们并将其存储在某个地方以供以后检查或作为 C++ 异常重新抛出。(我认为用 Cython 语法抛出 C++ 异常是不可能的,但你可以调用外部抛出辅助函数。)

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

显然,ObserverImpl也可以直接用C++编码。Py_INCREF,Py_DECREFPyObject_Call/PyObject_CallMethod是唯一需要的 Python API。

于 2013-06-12T16:13:03.473 回答