3

我真的很想知道是否有可能将 python 列表的引用传递给 boost::python c++ dll。我想要实现的是我在 python 中有一个列表,可以随时用 c++ 读取。假设您在 C++ 中有一个变量来保存对列表的引用。

有没有办法做到这一点?到目前为止,我只在 python 中找到了可以引用原始 c 类型的 ctypes,在这种情况下,这没有帮助。

我很高兴有任何建议或解决方法(一个简单的例子会很棒)

问候克里斯

4

1 回答 1

7

简而言之,Boost.Python 使用其 TypeWrappers 维护 Python 参数传递语义。因此,当将 Python 中的列表传递给公开的 C++ 函数时,可以通过接受 Python 列表作为boost::python::list对象来创建和维护引用。


详细的答案实际上有更多的深度。在深入研究之前,让我扩展一些语义以避免混淆。使用 Python 的垃圾收集和传递对象语义,一般的经验法则是将 Boost.Python TypeWrappers 视为智能指针。

  • 如果函数接受列表作为boost::python::list对象,则 C++ 现在具有对 Python 对象的引用。Python 列表的生命周期将延长到至少与booot::python::list对象一样长。
  • 如果 Python 列表被转换为不同的类型,例如std::vector,则 C++ 已构建到 Python 列表的副本。此副本与原始列表没有关联。

下面是一个简单的 C++ 模块示例,它可以传递一个 Python 列表,维护它的句柄,并打印它的内容:

#include <iostream> // std::cout
#include <utility>  // std::make_pair
#include <boost/foreach.hpp>
#include <boost/python.hpp>
#include <boost/python/stl_iterator.hpp>

boost::python::list list;

/// @brief Store handle to the list.
///
/// @param pylist Python list for which a handle will be maintained.
void set(const boost::python::list& pylist)
{
  // As the boost::python::list object is smart-pointer like, this
  // creates a reference to the python list, rather than creating a 
  // copy of the python list.
  list = pylist;
}

// Iterate over the current list, printing all ints.
void display()
{
  std::cout << "in display" << std::endl;
  typedef boost::python::stl_input_iterator<int> iterator_type;
  BOOST_FOREACH(const iterator_type::value_type& data, 
                std::make_pair(iterator_type(list), // begin
                               iterator_type()))    // end
  {
    std::cout << data << std::endl;
  }
}

BOOST_PYTHON_MODULE(example) {
  namespace python = boost::python;
  python::def("set",     &set);
  python::def("display", &display);
}

及其用法:

>>> import example
>>>
>>> x = range(2)
>>> x
[0, 1]
>>> example.set(x)
>>> example.display()
in display
0
1
>>> x[:] = range(7, 10)
>>> example.display()
in display
7
8
9

问题中引入的一种复杂性是希望随时阅读 C++ 中的 Python列表。在最复杂的情​​况下,任何时间都可能发生在任何时间点,来自 C++ 线程。

让我们从基础开始:Python 的全局解释器锁(GIL)。简而言之,GIL 是解释器周围的互斥体。如果一个线程正在做任何影响 python 托管对象的引用计数的事情,那么它需要获得 GIL。有时引用计数不是很透明,请考虑:

typedef boost::python::stl_input_iterator<int> iterator_type;
iterator_type iterator(list);

尽管boost::python::stl_input_iterator接受list为常量引用,但它会在构造函数中创建对 Python 列表的引用。

在前面的示例中,由于没有 C++ 线程,所有操作都在获取 GIL 时发生。但是,如果display()可以随时从 C++ 中调用,则需要进行一些设置。

首先,模块需要让 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_;
};

为了显示与 C++ 线程的交互,让我们向模块添加功能,允许应用程序为列表内容的显示时间安排延迟。

/// @brief Entry point for delayed display thread.
///
/// @param Delay in seconds.
void display_in_main(unsigned int seconds)
{
  boost::this_thread::sleep_for(boost::chrono::seconds(seconds));
  gil_lock lock; // Acquire GIL.
  display();     // Can safely modify python objects.
  // GIL released when lock goes out of scope.
}

/// @brief Schedule the list to be displayed.
///
/// @param Delay in seconds.
void display_in(unsigned int seconds)
{
  // Start detached thread.
  boost::thread(&display_in_main, seconds).detach();
}

这是完整的示例:

#include <iostream> // std::cout
#include <utility>  // std::make_pair
#include <boost/foreach.hpp>
#include <boost/python.hpp>
#include <boost/python/stl_iterator.hpp>
#include <boost/thread.hpp>

boost::python::list list;

/// @brief Store handle to the list.
///
/// @param pylist Python list for which a handle will be maintained.
void set(const boost::python::list& pylist)
{
  list = pylist;
}

// Iterate over the current list, printing all ints.
void display()
{
  std::cout << "in display" << std::endl;
  typedef boost::python::stl_input_iterator<int> iterator_type;
  BOOST_FOREACH(const iterator_type::value_type& data, 
                std::make_pair(iterator_type(list), // begin
                               iterator_type()))    // end
  {
    std::cout << data << std::endl;
  }
}

/// @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 Entry point for delayed display thread.
///
/// @param Delay in seconds.
void display_in_main(unsigned int seconds)
{
  boost::this_thread::sleep_for(boost::chrono::seconds(seconds));
  gil_lock lock; // Acquire GIL.
  display();     // Can safely modify python objects.
  // GIL released when lock goes out of scope.
}

/// @brief Schedule the list to be displayed.
///
/// @param Delay in seconds.
void display_in(unsigned int seconds)
{
  // Start detached thread.
  boost::thread(&display_in_main, seconds).detach();
}

BOOST_PYTHON_MODULE(example) {
  PyEval_InitThreads(); // Initialize GIL to support non-python threads.

  namespace python = boost::python;
  python::def("set",        &set);
  python::def("display",    &display);
  python::def("display_in", &display_in);
}

及其用法:

>>> import example
>>> from time import sleep
>>> 
>>> x = range(2)
>>> example.set(x)
>>> example.display()
in display
0
1
>>> example.display_in(3)
>>> x[:] = range(7, 10)
>>> print "sleeping"
sleeping
>>> sleep(6)
in display
7
8
9

当 Python 控制台在sleep(6)调用中阻塞 6 秒时,C++ 线程获取 GIL,显示 list 的内容x,并释放 GIL。

于 2013-07-16T18:52:43.323 回答