如何return一个std::vector<const T*>到python?
How to return a std::vector<const T*> to python?
我有一个 c++11 函数 return 是:
std::vector<const T*> f();
T 是我用 class_ 暴露给 python 的 c++ class。所有 T 实例都驻留在静态存储中,该存储将贯穿 python 进程。
我正在尝试将 f 公开为 python 函数
getAllTs()
那将 return python 对象包装器围绕 T。我选择 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;
}
};
我无法从容器构建元组目录。这可能吗?
我需要在UItable中显示这些T,进行排序,过滤。
T实例的最大数量是30000个奇数。在 C++11 中:
sizeof(T) = 24 bytes
在python3:
sys.getsizeof(t) = 72 bytes
什么是 return 我可以使用 where def'ing getAllTs 的策略来最小化重复,即让 python 添加最少的额外内容?
谢谢
将 std::vector<const T*>
公开给 Python
公开 std::vector<...>
的最简单方法是将其公开为 boost::python::class_
, and use the 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
class 被 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<const spam*>
被暴露并通过 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
将有一个 to-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 classes 实例的基本占用空间,它来自新样式 class、可变长度数据的大小、C++ 对象、vtable 指针,指向实例持有者的指针,以及实例持有者对齐的填充。如果您需要更小的占用空间,请考虑直接使用 Python/C API 进行类型创建,并使用 Boost.Python 与对象进行交互。
我有一个 c++11 函数 return 是:
std::vector<const T*> f();
T 是我用 class_ 暴露给 python 的 c++ class。所有 T 实例都驻留在静态存储中,该存储将贯穿 python 进程。
我正在尝试将 f 公开为 python 函数
getAllTs()
那将 return python 对象包装器围绕 T。我选择 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;
}
};
我无法从容器构建元组目录。这可能吗?
我需要在UItable中显示这些T,进行排序,过滤。
T实例的最大数量是30000个奇数。在 C++11 中:
sizeof(T) = 24 bytes
在python3:
sys.getsizeof(t) = 72 bytes
什么是 return 我可以使用 where def'ing getAllTs 的策略来最小化重复,即让 python 添加最少的额外内容?
谢谢
将 std::vector<const T*>
公开给 Python
公开 std::vector<...>
的最简单方法是将其公开为 boost::python::class_
, and use the 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
class 被 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<const spam*>
被暴露并通过 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
将有一个 to-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 classes 实例的基本占用空间,它来自新样式 class、可变长度数据的大小、C++ 对象、vtable 指针,指向实例持有者的指针,以及实例持有者对齐的填充。如果您需要更小的占用空间,请考虑直接使用 Python/C API 进行类型创建,并使用 Boost.Python 与对象进行交互。