我有问题重现你的结果,但这里是一些信息可能会解决此问题的帮助。
通过简单的类:
class Signal
{
public:
void connect() { std::cout << "connect called" << std::endl; }
private:
boost::signals2::signal<void()> signal_;
};
class MyClass
{
public:
Signal on_event;
};
而基本绑定:
namespace python = boost::python;
python::class_<Signal, boost::noncopyable>("Signal", python::no_init)
.def("connect", &Signal::connect)
;
python::class_<MyClass>("MyClass")
.def_readonly("on_event", &MyClass::on_event)
;
该代码编译失败。 当暴露一类,Boost.Python的默认行为的注册转换器。 这些转换器需要复制构造,以C ++类对象复制到存储可由Python对象进行管理的装置。 这种行为可以为一类通过提供禁用boost::noncopyable
作为参数传递给class_
类型。
在这种情况下, MyClass
结合不抑制拷贝构造函数。 Boost.Python的将尝试绑定中使用拷贝构造函数和失败,编译器错误,因为成员变量on_event
是不可拷贝。 Signal
是不可拷贝,因为它包含与一种类型的一个成员变量boost::signal2::signal
,从继承boost::noncopyable
。
添加boost:::noncopyable
的参数类型MyClass
的绑定允许代码进行编译。
namespace python = boost::python;
python::class_<Signal, boost::noncopyable>("Signal", python::no_init)
.def("connect", &Signal::connect)
;
python::class_<MyClass, boost::noncopyable>("MyClass")
.def_readonly("on_event", &MyClass::on_event)
;
用法:
>>> import example
>>> m = example.MyClass()
>>> m.on_event.connect()
connect called
>>> m.on_event = None
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
>>>
虽然这种设置允许所需的绑定和调用语法,它看起来好像是在最终目标的第一步。
我的道歉,如果这是太放肆。 然而,根据最近的其他问题,我想借此在最初的例子扩大到覆盖似乎是最终的目标时间:能够使用Python回调连接到signal2::signal
。 我将介绍两种不同的方法,如力学和复杂程度不同,但他们可能会提供洞察应考虑最终解决方案的详细信息。
只有Python的线程。
对于这第一个场景中,让我们假设只有Python的线程与库交互。
,保持它相对简单的一种方法是使用继承。 通过定义一个辅助启动Slot
类,它可以连接到Signal
。
class Slot
: public boost::python::wrapper<Slot>
{
public:
void operator()()
{
this->get_override("__call__")();
}
};
的Slot
类所继承boost::python::wrapper
,即unintrusively提供了钩子的一类,以允许Python类覆盖在基类的功能。
当调用类型连接boost::signals2::signal
,该信号可以争论复制到其内部列表。 因此,对于仿函数能够延长使用寿命是很重要的Slot
实例,只要其保持连接到signal
。 做到这一点最简单的方法是通过管理Slot
通过boost::shared_ptr
。
产生的Signal
类的样子:
class Signal
{
public:
template <typename Callback>
void connect(const Callback& callback)
{
signal_.connect(callback);
}
void operator()() { signal_(); }
private:
boost::signals2::signal<void()> signal_;
};
和一个辅助功能,有助于保持Signal::connect
一般,在其他情况下,C ++类型需要连接到它。
void connect_slot(Signal& self,
const boost::shared_ptr<Slot>& slot)
{
self.connect(boost::bind(&Slot::operator(), slot));
}
这将导致以下绑定:
BOOST_PYTHON_MODULE(example) {
namespace python = boost::python;
python::class_<Signal, boost::noncopyable>("Signal", python::no_init)
.def("connect", &connect_slot)
.def("__call__", &Signal::operator())
;
python::class_<MyClass, boost::noncopyable>("MyClass")
.def_readonly("on_event", &MyClass::on_event)
;
python::class_<Slot, boost::shared_ptr<Slot>,
boost::noncopyable>("Slot")
.def("__call__", python::pure_virtual(&Slot::operator()))
;
}
而它的用法如下:
>>> from example import *
>>> class Foo(Slot):
... def __call__(self):
... print "Foo::__call__"
...
>>> m = MyClass()
>>> foo = Foo()
>>> m.on_event.connect(foo)
>>> m.on_event()
Foo::__call__
>>> foo = None
>>> m.on_event()
Foo::__call__
虽然成功的,它具有不Python的一个不幸的特点。 例如:
>>> def spam():
... print "spam"
...
>>> m = MyClass()
>>> m.on_event.connect(spam)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
Boost.Python.ArgumentError: Python argument types in
Signal.connect(Signal, function)
did not match C++ signature:
connect(Signal {lvalue}, boost::shared_ptr<Slot>)
这将是理想的,如果任何可调用对象可以连接到信号。 一个简单的方法这一点,猴修补绑定在Python。 是透明的最终用户:
- 改变C ++从结合模块名字
example
到_example
。 请务必同时更改库名。 - 创建
example.py
将补丁Signal.connect()
的论点包装成从继承的类型Slot
。
example.py
可能是这个样子:
from _example import *
class _SlotWrap(Slot):
def __init__(self, fn):
self.fn = fn
Slot.__init__(self)
def __call__(self):
self.fn()
def _signal_connect(fn):
def decorator(self, slot):
# If the slot is not an instance of Slot, then aggregate it
# in SlotWrap.
if not isinstance(slot, Slot):
slot = _SlotWrap(slot)
# Invoke the decorated function with the slot.
return fn(self, slot)
return decorator
# Patch Signal.connect.
Signal.connect = _signal_connect(Signal.connect)
修补是无缝的最终用户。
>>> from example import *
>>> def spam():
... print "spam"
...
>>> m = MyClass()
>>> m.on_event.connect(spam)
>>> m.on_event()
spam
有了这个补丁,任何调用类型可以连接到Signal
,而不必从明确继承Slot
。 因此,它变得比初始溶液更Python化。 永远不要低估了利益在保持简单的绑定和非Python的,但修补他们在蟒蛇Python的。
Python和C ++线程。
在接下来的情景,让我们考虑其中C ++线程与Python交互的情况下。 例如,一个C ++线程可以被设定的一段时间之后调用信号。
这个例子可以成为相当棘手,所以让我们从基础开始:Python的全局解释器锁 (GIL)。 总之,GIL是围绕着解释互斥。 如果一个线程在做任何影响蟒蛇管理对象的引用计数,那么它需要已经获得了GIL。 在前面的例子中,由于没有C ++线程,而GIL已经收购了发生的一切行动。 尽管这相当简单,它可以相当迅速地变得复杂。
首先,该模块需要有Python的初始化GIL的线程。
BOOST_PYTHON_MODULE(example) {
PyEval_InitThreads(); // Initialize GIL to support non-python threads.
...
}
为方便起见,让我们创建一个简单的类来帮助管理GIL:
/// @brief RAII class used to lock and unlock the GIL.
class gil_lock
{
public:
gil_lock() { state_ = PyGILState_Ensure(); }
~gil_lock() { PyGILState_Release(state_); }
private:
PyGILState_STATE state_;
};
该线程将被调用MyClass
的信号。 因此,它需要延长的寿命MyClass
,而线程是活动的。 一个很好的人选来完成,这是通过管理MyClass
一个shared_ptr
。
让我们找出当C ++线程将需要GIL:
-
MyClass
被删除shared_ptr
。 -
boost::signals2::signal
可以使连接的对象的额外副本,当信号完成并发调用。 - 调用一个Python反对通过连接
boost::signals2::signal
。 回调势必会影响Python对象。 例如, self
设置于参数__call__
方法将增加和减少一个对象的引用计数。
支撑MyClass
正在从C ++线程删除。
为了保证当GIL举行MyClass
被删除shared_ptr
从C ++线程内,一个定制删除是必需的。 这也需要绑定,以抑制默认的构造函数,并使用自定义构造函数来代替。
/// @brief Custom deleter.
template <typename T>
struct py_deleter
{
void operator()(T* t)
{
gil_lock lock;
delete t;
}
};
/// @brief Create Signal with a custom deleter.
boost::shared_ptr<MyClass> create_signal()
{
return boost::shared_ptr<MyClass>(
new MyClass(),
py_deleter<MyClass>());
}
...
BOOST_PYTHON_MODULE(example) {
...
python::class_<MyClass, boost::shared_ptr<MyClass>,
boost::noncopyable>("MyClass", python::no_init)
.def("__init__", python::make_constructor(&create_signal))
.def_readonly("on_event", &MyClass::on_event)
;
}
线程本身。
该线程的功能是相当简单:然后它睡觉调用信号。 然而,了解GIL的情况下是很重要的。
/// @brief Wait for a period of time, then invoke the
/// signal on MyClass.
void call_signal(boost::shared_ptr<MyClass>& shared_class,
unsigned int seconds)
{
// The shared_ptr was created by the caller when the GIL was
// locked, and is accepted as a reference to avoid modifying
// it while the GIL is not locked.
// Sleep without the GIL so that other python threads are able
// to run.
boost::this_thread::sleep_for(boost::chrono::seconds(seconds));
// We do not want to hold the GIL while invoking C++-specific
// slots connected to the signal. Thus, it is the responsibility of
// python slots to lock the GIL. Additionally, the potential
// copying of slots internally by the signal will be handled through
// another mechanism.
shared_class->on_event();
// The shared_class has a custom deleter that will lock the GIL
// when deletion needs to occur.
}
/// @brief Function that will be exposed to python that will create
/// a thread to call the signal.
void spawn_signal_thread(boost::shared_ptr<MyClass> self,
unsigned int seconds)
{
// The caller owns the GIL, so it is safe to make copies. Thus,
// spawn off the thread, binding the arguments via copies. As
// the thread will not be joined, detach from the thread.
boost::thread(boost::bind(&call_signal, self, seconds)).detach();
}
而MyClass
绑定更新。
python::class_<MyClass, boost::shared_ptr<MyClass>,
boost::noncopyable>("MyClass", python::no_init)
.def("__init__", python::make_constructor(&create_signal))
.def("signal_in", &spawn_signal_thread)
.def_readonly("on_event", &MyClass::on_event)
;
boost::signals2::signal
和Python对象进行交互。
boost::signals2::signal
可进行复制被调用时。 此外,可能存在连接到信号C ++时隙,所以这将是理想的,以不具有GIL锁定而信号被调用。 然而, signal
不提供钩子允许我们创建时隙的副本或调用时隙之前获取GIL。
为了增加复杂性,当绑定暴露接受一个C ++类与C ++函数HeldType
这不是一个智能指针,然后Boost.Python的将提取非参考从基准计数蟒对象计数C ++的对象。 它可以安全地做到这一点,因为调用线程,在Python中,有GIL。 为了保持一个引用计数插槽试图从Python的连接,以及允许任何调用类型的连接,我们可以使用不透明类型boost::python::object
。
为了避免signal
创建提供的复印件boost::python::object
,可以创建副本boost::python::object
,这样的引用计数保持准确,并通过管理复制shared_ptr
。 这使得signal
可以自由创建副本shared_ptr
而不是创建boost::python::object
没有GIL。
这GIL安全插槽可在一个辅助类封装。
/// @brief Helper type that will manage the GIL for a python slot.
class py_slot
{
public:
/// @brief Constructor that assumes the caller has the GIL locked.
py_slot(const boost::python::object& object)
: object_(new boost::python::object(object), // GIL locked, so copy.
py_deleter<boost::python::object>()) // Delete needs GIL.
{}
void operator()()
{
// Lock the gil as the python object is going to be invoked.
gil_lock lock;
(*object_)();
}
private:
boost::shared_ptr<boost::python::object> object_;
};
一个辅助函数将被暴露在Python的帮助适应的类型。
/// @brief Signal connect helper.
void signal_connect(Signal& self,
boost::python::object object)
{
self.connect(boost::bind(&py_slot::operator(), py_slot(object)));
}
而更新的绑定暴露辅助函数:
python::class_<Signal, boost::noncopyable>("Signal", python::no_init)
.def("connect", &signal_connect)
.def("__call__", &Signal::operator())
;
最终的解决方案是这样的:
#include <boost/bind.hpp>
#include <boost/python.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/signals2/signal.hpp>
#include <boost/thread.hpp>
class Signal
{
public:
template <typename Callback>
void connect(const Callback& callback)
{
signal_.connect(callback);
}
void operator()() { signal_(); }
private:
boost::signals2::signal<void()> signal_;
};
class MyClass
{
public:
Signal on_event;
};
/// @brief RAII class used to lock and unlock the GIL.
class gil_lock
{
public:
gil_lock() { state_ = PyGILState_Ensure(); }
~gil_lock() { PyGILState_Release(state_); }
private:
PyGILState_STATE state_;
};
/// @brief Custom deleter.
template <typename T>
struct py_deleter
{
void operator()(T* t)
{
gil_lock lock;
delete t;
}
};
/// @brief Create Signal with a custom deleter.
boost::shared_ptr<MyClass> create_signal()
{
return boost::shared_ptr<MyClass>(
new MyClass(),
py_deleter<MyClass>());
}
/// @brief Wait for a period of time, then invoke the
/// signal on MyClass.
void call_signal(boost::shared_ptr<MyClass>& shared_class,
unsigned int seconds)
{
// The shared_ptr was created by the caller when the GIL was
// locked, and is accepted as a reference to avoid modifying
// it while the GIL is not locked.
// Sleep without the GIL so that other python threads are able
// to run.
boost::this_thread::sleep_for(boost::chrono::seconds(seconds));
// We do not want to hold the GIL while invoking C++-specific
// slots connected to the signal. Thus, it is the responsibility of
// python slots to lock the GIL. Additionally, the potential
// copying of slots internally by the signal will be handled through
// another mechanism.
shared_class->on_event();
// The shared_class has a custom deleter that will lock the GIL
// when deletion needs to occur.
}
/// @brief Function that will be exposed to python that will create
/// a thread to call the signal.
void spawn_signal_thread(boost::shared_ptr<MyClass> self,
unsigned int seconds)
{
// The caller owns the GIL, so it is safe to make copies. Thus,
// spawn off the thread, binding the arguments via copies. As
// the thread will not be joined, detach from the thread.
boost::thread(boost::bind(&call_signal, self, seconds)).detach();
}
/// @brief Helepr type that will manage the GIL for a python slot.
struct py_slot
{
public:
/// @brief Constructor that assumes the caller has the GIL locked.
py_slot(const boost::python::object& object)
: object_(new boost::python::object(object), // GIL locked, so copy.
py_deleter<boost::python::object>()) // Delete needs GIL.
{}
void operator()()
{
// Lock the gil as the python object is going to be invoked.
gil_lock lock;
(*object_)();
}
private:
boost::shared_ptr<boost::python::object> object_;
};
/// @brief Signal connect helper.
void signal_connect(Signal& self,
boost::python::object object)
{
self.connect(boost::bind(&py_slot::operator(), py_slot(object)));
}
BOOST_PYTHON_MODULE(example) {
PyEval_InitThreads(); // Initialize GIL to support non-python threads.
namespace python = boost::python;
python::class_<Signal, boost::noncopyable>("Signal", python::no_init)
.def("connect", &signal_connect)
.def("__call__", &Signal::operator())
;
python::class_<MyClass, boost::shared_ptr<MyClass>,
boost::noncopyable>("MyClass", python::no_init)
.def("__init__", python::make_constructor(&create_signal))
.def("signal_in", &spawn_signal_thread)
.def_readonly("on_event", &MyClass::on_event)
;
}
和测试脚本( test.py
):
from time import sleep
from example import *
def spam():
print "spam"
m = MyClass()
m.on_event.connect(spam)
m.on_event()
m.signal_in(2)
m = None
print "Sleeping"
sleep(5)
print "Done sleeping"
结果如下:
spam
Sleeping
spam
Done sleeping
总之,当一个对象通过Boost.Python的层通过,需要时间来考虑如何管理其寿命和它要使用的上下文。 这通常需要了解如何使用其他库将处理对象。 这不是一个简单的问题,并提供解决方案Python的可能是一个挑战。