如何在 std::vector 通过 boost::python 的迭代期间使 python 不从 boost::shared_ptr 到 create/copy pyobject?
How to make python not to create/copy pyobject from boost::shared_ptr during iteration on std::vector via boost::python?
我有一个对象和其中的 boost::shared_ptr-s 个对象的 std::vector,我想使用 boost::python 将其转移到 python。
这里的问题是,在对 python 中暴露的向量进行迭代期间,我每次都为同一个对象获得不同的地址。
据我了解,python 在每次迭代时通过我的 boost::shared_ptr 创建 pyobject,并且在迭代之后(实际上是在第二次迭代之后)垃圾收集器释放该地址,尽管元素是 boost::shared_ptr-s。
当元素在 python 的列表中时,它们具有在迭代期间不会改变的唯一地址。这就是我想要实现的目标。
我试图通过在 Whosebug 中获取不同的提升文档和不同的 QA 来上网,但未能解决我的问题。
我创建了一个小例子来演示这个问题。
这是 C++ 代码:
#include <vector>
#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>
#include <boost/python.hpp>
namespace bp = boost::python;
class A {
public:
A() : m_int(0) { }
A(int numb) : m_int(numb) { }
int get_m() const { return m_int; }
private:
int m_int;
};
std::ostream& operator<<(std::ostream& out, const A& obj) {
out << obj.get_m();
return out;
}
using APtr = boost::shared_ptr<A>;
using ACont = std::vector<APtr>;
using AContPtr = boost::shared_ptr<ACont>;
APtr CreateA() {
return boost::make_shared<A>();
}
APtr CreateAFromNumb(int numb) {
return boost::make_shared<A>(numb);
}
AContPtr CreateEmptyACont() {
return boost::make_shared<ACont>();
}
AContPtr CreateFullACont(int size) {
ACont vec{};
for (int i = 0; i < size; ++i) {
vec.push_back(boost::make_shared<A>(i));
}
return boost::make_shared<ACont>(vec);
}
BOOST_PYTHON_MODULE(example) {
bp::class_<A, APtr, boost::noncopyable>("A", bp::no_init)
.def("__init__", bp::make_constructor(CreateA))
.def("__init__", bp::make_constructor(CreateAFromNumb))
.def(bp::self_ns::str(bp::self))
;
bp::register_ptr_to_python<APtr>();
bp::class_<ACont, AContPtr, boost::noncopyable>("ACont", bp::no_init)
.def("__init__", bp::make_constructor(CreateEmptyACont))
.def("__init__", bp::make_constructor(CreateFullACont))
.def("__iter__", bp::iterator<ACont>())
.def("__len__", &ACont::size)
;
bp::register_ptr_to_python<AContPtr>();
}
下面是它在python中的用法:
#!/usr/bin/env python
from example import *
vec = ACont(4)
print len(vec)
print "VEC", vec
for x in vec:
print x,
print repr(x)
print "VEC", vec
for x in vec:
print x,
print repr(x)
l = [x for x in vec]
print "LIST"
for x in l:
print x,
print repr(x)
print "LIST"
for x in l:
print x,
print repr(x)
当遍历公开的 std::vector/boost::shared_ptr 时,我在两次连续的迭代中得到了相同元素的不同地址。
- Python 在第一次迭代时为第一项创建 pyobject 并且
显示它的地址。
- 在第二次迭代中,python 为另一个地址上的第二个元素创建另一个 pyobject。
- 垃圾收集器发现第一个地址不再可用并释放它。
- 在第三次迭代中,python 在与第一个元素相同的地址中为第三个元素创建另一个 pyobject,因为 gc 刚刚释放了它。
- 以此类推
但是对相同元素列表的迭代是正确完成的,因为 python 没有 create/destruct pyobjects,因为它们在列表本身中有引用。
这是 python 运行 的输出:
4
VEC <example.ACont object at 0x7ffff7ea9578>
0 <example.A object at 0x7ffff7eb27d0> # address 1
1 <example.A object at 0x7ffff7eb2848> # address 2
2 <example.A object at 0x7ffff7eb27d0> # address 1 since gc just have freed it
3 <example.A object at 0x7ffff7eb2848> # so on
VEC <example.ACont object at 0x7ffff7ea9578>
0 <example.A object at 0x7ffff7eb27d0>
1 <example.A object at 0x7ffff7eb2848>
2 <example.A object at 0x7ffff7eb27d0>
3 <example.A object at 0x7ffff7eb2848>
LIST
0 <example.A object at 0x7ffff7eb27d0> # address 1
1 <example.A object at 0x7ffff7eb2848> # address 2
2 <example.A object at 0x7ffff7eb28c0> # address 3
3 <example.A object at 0x7ffff7eb2938> # address 4
LIST
0 <example.A object at 0x7ffff7eb27d0>
1 <example.A object at 0x7ffff7eb2848>
2 <example.A object at 0x7ffff7eb28c0>
3 <example.A object at 0x7ffff7eb2938>
总结一下,考虑到这些对象是 boost::shared_ptr-s 的事实,我想知道是否有一种方法不为在 C++ 中创建的每个元素在 python 中创建 pyobjects一些对象。
提前谢谢你。
P.S。我正在使用 boost 版本 1.60 和 python 2.7(anaconda 包)。
所以,经过长时间的调查,我发现我要解决的任务并没有真正的解决方案。这是因为 python 必须注册每个刚出现在其环境中的对象(在别处创建,而不是在 python 中创建)以了解它。因此,关于垃圾收集器的其余场景就像问题中描述的那样。 boost/python 和 swig 都无法处理这种情况。
但是,作为对此的手动解决方案,为了解决寻址问题,我创建了某种地址映射功能。由于我的问题与对象地址有关,我将 C++ 地址转移到 python 以便查看真实对象的地址而不是 python 的地址。
此外,由于我需要处理地址的散列功能,所以我在那里做了同样的事情。这是我的更新
// Same code as previously
std::string GetAddress(const APtr& a_ptr) {
std::string repr{"<example.A object at "};
std::stringstream sstream;
sstream << std::hex << std::showbase << reinterpret_cast<std::uintptr_t>(a_ptr.get());
repr += sstream.str() + ">";
return repr;
}
static std::size_t Hash(const APtr& a_ptr) {
std::hash<std::uintptr_t> hasher;
return hasher(reinterpret_cast<std::uintptr_t>(a_ptr.get()));
}
BOOST_PYTHON_MODULE(example) {
bp::class_<A, APtr, boost::noncopyable>("A", bp::no_init)
.def("__init__", bp::make_constructor(CreateA))
.def("__init__", bp::make_constructor(CreateAFromNumb))
.def("__repr__", GetAddress)
.def("__hash__", Hash)
.def(bp::self_ns::str(bp::self))
;
bp::register_ptr_to_python<APtr>();
bp::class_<ACont, AContPtr, boost::noncopyable>("ACont", bp::no_init)
.def("__init__", bp::make_constructor(CreateEmptyACont))
.def("__init__", bp::make_constructor(CreateFullACont))
.def("__iter__", bp::iterator<ACont>())
.def("__len__", &ACont::size)
;
bp::register_ptr_to_python<AContPtr>();
}
无论如何,感谢大家努力提供帮助。 :)
我有一个对象和其中的 boost::shared_ptr-s 个对象的 std::vector,我想使用 boost::python 将其转移到 python。 这里的问题是,在对 python 中暴露的向量进行迭代期间,我每次都为同一个对象获得不同的地址。 据我了解,python 在每次迭代时通过我的 boost::shared_ptr 创建 pyobject,并且在迭代之后(实际上是在第二次迭代之后)垃圾收集器释放该地址,尽管元素是 boost::shared_ptr-s。 当元素在 python 的列表中时,它们具有在迭代期间不会改变的唯一地址。这就是我想要实现的目标。 我试图通过在 Whosebug 中获取不同的提升文档和不同的 QA 来上网,但未能解决我的问题。 我创建了一个小例子来演示这个问题。 这是 C++ 代码:
#include <vector>
#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>
#include <boost/python.hpp>
namespace bp = boost::python;
class A {
public:
A() : m_int(0) { }
A(int numb) : m_int(numb) { }
int get_m() const { return m_int; }
private:
int m_int;
};
std::ostream& operator<<(std::ostream& out, const A& obj) {
out << obj.get_m();
return out;
}
using APtr = boost::shared_ptr<A>;
using ACont = std::vector<APtr>;
using AContPtr = boost::shared_ptr<ACont>;
APtr CreateA() {
return boost::make_shared<A>();
}
APtr CreateAFromNumb(int numb) {
return boost::make_shared<A>(numb);
}
AContPtr CreateEmptyACont() {
return boost::make_shared<ACont>();
}
AContPtr CreateFullACont(int size) {
ACont vec{};
for (int i = 0; i < size; ++i) {
vec.push_back(boost::make_shared<A>(i));
}
return boost::make_shared<ACont>(vec);
}
BOOST_PYTHON_MODULE(example) {
bp::class_<A, APtr, boost::noncopyable>("A", bp::no_init)
.def("__init__", bp::make_constructor(CreateA))
.def("__init__", bp::make_constructor(CreateAFromNumb))
.def(bp::self_ns::str(bp::self))
;
bp::register_ptr_to_python<APtr>();
bp::class_<ACont, AContPtr, boost::noncopyable>("ACont", bp::no_init)
.def("__init__", bp::make_constructor(CreateEmptyACont))
.def("__init__", bp::make_constructor(CreateFullACont))
.def("__iter__", bp::iterator<ACont>())
.def("__len__", &ACont::size)
;
bp::register_ptr_to_python<AContPtr>();
}
下面是它在python中的用法:
#!/usr/bin/env python
from example import *
vec = ACont(4)
print len(vec)
print "VEC", vec
for x in vec:
print x,
print repr(x)
print "VEC", vec
for x in vec:
print x,
print repr(x)
l = [x for x in vec]
print "LIST"
for x in l:
print x,
print repr(x)
print "LIST"
for x in l:
print x,
print repr(x)
当遍历公开的 std::vector/boost::shared_ptr
- Python 在第一次迭代时为第一项创建 pyobject 并且 显示它的地址。
- 在第二次迭代中,python 为另一个地址上的第二个元素创建另一个 pyobject。
- 垃圾收集器发现第一个地址不再可用并释放它。
- 在第三次迭代中,python 在与第一个元素相同的地址中为第三个元素创建另一个 pyobject,因为 gc 刚刚释放了它。
- 以此类推
但是对相同元素列表的迭代是正确完成的,因为 python 没有 create/destruct pyobjects,因为它们在列表本身中有引用。 这是 python 运行 的输出:
4
VEC <example.ACont object at 0x7ffff7ea9578>
0 <example.A object at 0x7ffff7eb27d0> # address 1
1 <example.A object at 0x7ffff7eb2848> # address 2
2 <example.A object at 0x7ffff7eb27d0> # address 1 since gc just have freed it
3 <example.A object at 0x7ffff7eb2848> # so on
VEC <example.ACont object at 0x7ffff7ea9578>
0 <example.A object at 0x7ffff7eb27d0>
1 <example.A object at 0x7ffff7eb2848>
2 <example.A object at 0x7ffff7eb27d0>
3 <example.A object at 0x7ffff7eb2848>
LIST
0 <example.A object at 0x7ffff7eb27d0> # address 1
1 <example.A object at 0x7ffff7eb2848> # address 2
2 <example.A object at 0x7ffff7eb28c0> # address 3
3 <example.A object at 0x7ffff7eb2938> # address 4
LIST
0 <example.A object at 0x7ffff7eb27d0>
1 <example.A object at 0x7ffff7eb2848>
2 <example.A object at 0x7ffff7eb28c0>
3 <example.A object at 0x7ffff7eb2938>
总结一下,考虑到这些对象是 boost::shared_ptr-s 的事实,我想知道是否有一种方法不为在 C++ 中创建的每个元素在 python 中创建 pyobjects一些对象。 提前谢谢你。
P.S。我正在使用 boost 版本 1.60 和 python 2.7(anaconda 包)。
所以,经过长时间的调查,我发现我要解决的任务并没有真正的解决方案。这是因为 python 必须注册每个刚出现在其环境中的对象(在别处创建,而不是在 python 中创建)以了解它。因此,关于垃圾收集器的其余场景就像问题中描述的那样。 boost/python 和 swig 都无法处理这种情况。
但是,作为对此的手动解决方案,为了解决寻址问题,我创建了某种地址映射功能。由于我的问题与对象地址有关,我将 C++ 地址转移到 python 以便查看真实对象的地址而不是 python 的地址。
此外,由于我需要处理地址的散列功能,所以我在那里做了同样的事情。这是我的更新
// Same code as previously
std::string GetAddress(const APtr& a_ptr) {
std::string repr{"<example.A object at "};
std::stringstream sstream;
sstream << std::hex << std::showbase << reinterpret_cast<std::uintptr_t>(a_ptr.get());
repr += sstream.str() + ">";
return repr;
}
static std::size_t Hash(const APtr& a_ptr) {
std::hash<std::uintptr_t> hasher;
return hasher(reinterpret_cast<std::uintptr_t>(a_ptr.get()));
}
BOOST_PYTHON_MODULE(example) {
bp::class_<A, APtr, boost::noncopyable>("A", bp::no_init)
.def("__init__", bp::make_constructor(CreateA))
.def("__init__", bp::make_constructor(CreateAFromNumb))
.def("__repr__", GetAddress)
.def("__hash__", Hash)
.def(bp::self_ns::str(bp::self))
;
bp::register_ptr_to_python<APtr>();
bp::class_<ACont, AContPtr, boost::noncopyable>("ACont", bp::no_init)
.def("__init__", bp::make_constructor(CreateEmptyACont))
.def("__init__", bp::make_constructor(CreateFullACont))
.def("__iter__", bp::iterator<ACont>())
.def("__len__", &ACont::size)
;
bp::register_ptr_to_python<AContPtr>();
}
无论如何,感谢大家努力提供帮助。 :)