【问题标题】:How to return a std::vector<const T*> to python?如何将 std::vector<const T*> 返回给 python?
【发布时间】:2015-09-11 20:34:29
【问题描述】:

我有一个 c++11 函数,它返回一个:

std::vector<const T*> f();

T 是一个 c++ 类,我使用 class_ 向 python 公开。所有的 T 实例都驻留在静态存储中,这些存储将在 python 进程的整个生命周期中持续存在。

我正在尝试将 f 公开为 python 函数

getAllTs()

这将返回围绕 T 的 python 对象包装器。我选择 T* 作为 class_ 的持有类型。

我正在使用这个糟糕的半泛型函数将 std::vector 转换为 python 元组:

template <typename Cont>
struct stdcont_to_python_tuple
{
  static PyObject* convert(const Cont& container)
  {
    boost::python::list lst;
    for (const auto& elt: container)
      lst.append(elt);

    return boost::python::incref( boost::python::tuple(lst).ptr() );
  }

  static PyTypeObject const* get_pytype()
  {
    return &PyTuple_Type;
  }
};

我无法从容器构造元组目录。这可能吗?

我需要在 UI 表格中显示这些 T,执行排序、过滤。

T 实例的最大数量是 30000 奇数。在 c++11 中:

sizeof(T) = 24 bytes

在python3中:

sys.getsizeof(t) = 72 bytes

我可以使用什么退货政策来定义 getAllTs 以最大程度地减少重复,即 python 添加的额外内容最少?

谢谢

【问题讨论】:

  • 这个问题让我怀疑这是XY problem。提出问题并描述正在解决的问题可能更有成效。

标签: vector boost-python


【解决方案1】:

将 std::vector&lt;const T*&gt; 暴露给 Python

公开std::vector&lt;...&gt; 的最简单方法是将其公开为boost::python::class_,并使用vector_indexing_suite 提供类似Python 序列的接口。

std::vector<const spam*> get_spams() { ... }

BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;

  // Expose `spam`
  python::class_<spam, spam*>("Spam");

  // Expose `std::vector<const spam*>`
  python::class_<std::vector<const spam*>>("Spams")
    .def(python::vector_indexing_suite<std::vector<const spam*>>())
    ;

  python::def("get_spams", &get_spams);
}

但是,使用 const spam* 类型可能不会像人们希望的那样富有成效。首先,Python 没有const 的概念。此外,当将spam 类公开为由spam* 持有时,自动to-python 和from-python 转换器用于spam*,而不是const spam*。由于索引套件在索引期间返回代理,这可能不会立即显现出来,但在迭代时会变得明显,因为迭代器的值将尝试转换为 Python。

spams = example.get_spams()
# Access by index returns a proxy.  It does not perform a
# spam-to-python conversion.
spams[0].perform()
# The iterator's value will be returned, requiring a 
# spam-to-python conversion.
for spam in spams:
    pass

要解决这个问题,可以为const spam* 注册一个明确的到python 转换。我强烈考虑重新检查const spam* 是否必要,或者将spam 公开为不同类型是否更容易(例如boost::shared_ptr 带有空删除器)。不管怎样,这里有一个完整的例子demonstrating这个功能:

#include <iostream>
#include <boost/python.hpp>
#include <boost/python/suite/indexing/vector_indexing_suite.hpp>

/// Mocks...
struct spam
{
  spam() { std::cout << "spam() " << this << std::endl; }
  ~spam() { std::cout << "~spam() " << this << std::endl; }
  void perform() { std::cout << "spam::perform() " << this << std::endl; }
};

namespace {
  std::array<spam, 3> spams;
} // namespace

std::vector<const spam*> get_spams()
{
  std::vector<const spam*> result;
  for (auto& spam: spams)
  {
    result.push_back(&spam);
  }
  return result;
}

/// @brief Convert for converting `const spam*` to `Spam`.
struct const_spam_ptr_to_python
{
  static PyObject* convert(const spam* ptr)
  {
    namespace python = boost::python;
    python::object object(python::ptr(ptr));
    return python::incref(object.ptr());
  }
};

BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;

   // Enable `const spam*` to `Spam` converter.
  python::to_python_converter<const spam*, const_spam_ptr_to_python>();

  // Expose `spam`.
  python::class_<spam, spam*>("Spam", python::no_init)
    .def("perform", &spam::perform)
    ;

  // Expose `std::vector<const spam*>`.
  python::class_<std::vector<const spam*>>("Spams")
    .def(python::vector_indexing_suite<std::vector<const spam*>>())
    ;

  python::def("get_spams", &get_spams);
}

互动使用:

>>> import example
spam() 0x7ffbec612218
spam() 0x7ffbec612219
spam() 0x7ffbec61221a
>>> spams = example.get_spams()
>>> for index, spam in enumerate(spams):
...     spams[index].perform()
...     spam.perform()
...
spam::perform() 0x7ffbec612218
spam::perform() 0x7ffbec612218
spam::perform() 0x7ffbec612219
spam::perform() 0x7ffbec612219
spam::perform() 0x7ffbec61221a
spam::perform() 0x7ffbec61221a
~spam() 0x7ffbec61221a
~spam() 0x7ffbec612219
~spam() 0x7ffbec612218

从 C++ 容器构造 Python 元组

boost::python::tuple 可以从序列构造。如果提供了 C++ 对象,则它需要可转换为实现 Python 迭代器协议的 Python 类型。例如,在上面的例子中,std::vector&lt;const spam*&gt; 被暴露并通过vector_indexing_suite 提供了一个类似序列的接口,可以将get_spams() 写为:

boost::python::tuple get_spams()
{
  std::vector<const spam*> result;
  for (auto& spam: spams)
  {
    result.push_back(&spam);
  }
  return boost::python::tuple(result);
}

或者,可以使用boost/python/iterator.hpp 文件提供的类型和函数从 C++ 容器或迭代器创建 Python 迭代器。这些示例演示了将 std::pair 的迭代器、开始和结束暴露给 Python。由于std::pair 将具有可用的到python 转换,因此可以从std::pair 构造boost::python::tuple。

这是一个竞争示例demonstrating 这种方法:

#include <boost/python.hpp>

/// @brief Returns a tuple, constructing it form a range.
template <typename Container>
boost::python::tuple container_to_tuple(Container& container)
{
  namespace python = boost::python;
  return python::tuple(std::make_pair(
      container.begin(), container.end()));
}

BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;

  // Expose an int range.
  typedef std::vector<int>::iterator vector_int_iterator;
  typedef std::pair<vector_int_iterator, vector_int_iterator> vector_int_range;
  python::class_<vector_int_range>("IntRange")
    .def("__iter__", python::range(
        &vector_int_range::first, &vector_int_range::second))
      ;

   // Return a tuple of ints.
  python::def("get_ints", +[] {
    std::vector<int> result;
    result.push_back(21);
    result.push_back(42);
    return container_to_tuple(result);
  });
}

互动使用:

>>> import example
>>> ints = example.get_ints()
>>> assert(isinstance(ints, tuple))
>>> assert(ints == (21, 42))

内存占用

如果 C++ 对象已经存在,则可以让 python::object 通过指针引用它,这将减少重复使用一些整体内存。但是,没有选项可以减少 Boost.Python 类的实例的基本占用空间,它来自新样式类、可变长度数据的大小、C++ 对象、vtable 指针、指向实例持有者的指针以及实例持有者的填充结盟。如果您需要更小的占用空间,请考虑直接使用 Python/C API 进行类型创建,并使用 Boost.Python 与对象进行交互。

【讨论】:

    猜你喜欢
    • 2019-10-20
    • 2017-04-07
    • 2021-06-02
    • 1970-01-01
    • 2016-03-31
    • 2019-11-07
    • 2022-10-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多