我在C ++中有A,B,C类。 B和C派生自A.我在c ++中有函数返回B和C的向量:如std::vector<A*> getAllObjects()
。我使用swig生成Python包装器。然后我在Python中调用getAllObjects(),如下所示:
objects = getAllObjects()
for obj in objects:
if isinstance(obj, B):
print("OK")
elif isinstance(obj, C):
print("OK")
我从迭代器获取的对象是实例A,但它应该是B或C.如何解决问题?
答案 0 :(得分:3)
除了类型层次结构之外,还需要某些更多内容。通常在Python / SWIG场景中,下列之一就足够了:
virtual
函数(即RTTI)我正在努力假设第一种类型已经足够,但即使对于其他情况,它也不难以适应。
为了说明这一点,我写了以下头文件:
class A {
public:
virtual ~A() {}
};
class B : public A {
};
class C: public A {
};
鉴于此头文件,在纯C ++中我们可以使用RTTI执行以下操作:
#include "test.hh"
#include <typeinfo>
#include <iostream>
int main() {
const auto& t1 = typeid(A);
const auto& t2 = typeid(B);
const auto& t3 = typeid(C);
A *a = new A;
A *b = new B;
A *c = new C;
const auto& at = typeid(*a);
const auto& bt = typeid(*b);
const auto& ct = typeid(*c);
std::cout << t1.name() << "\n";
std::cout << t2.name() << "\n";
std::cout << t3.name() << "\n";
std::cout << at.name() << "\n";
std::cout << bt.name() << "\n";
std::cout << ct.name() << "\n";
}
这说明我们尝试解决的问题(它究竟是什么类型?)实际上是使用标准C ++解决的。
值得注意的是,通过使用std::vector
迭代而不仅仅是返回单个A*
的函数,问题变得稍微复杂一些。如果我们只是处理函数的返回值,我们会写typemap(out)
。但是在std::vector<A*>
的情况下,可以自定义返回的迭代行为并插入额外的代码以确保Python知道派生类型而不仅仅是基础。 SWIG具有类型特征机制,大多数标准容器使用它来帮助它们进行常见用途(例如迭代)而不会过多重复。 (作为参考,我认为这是在std_common.i中。)
因此,基本计划是使用SWIG引入的特征类型来挂钩迭代过程的输出(SwigPyIterator
,在这种情况下实现为SwigPyIteratorClosed_T
)。在该钩子中,我们不是盲目地使用A*
的SWIG类型信息,而是使用typeid
在std::map
中动态查找类型。此映射在模块内部维护。如果我们在该映射中找到任何内容,我们将使用它来返回更多派生的Python对象,就像Python程序员所期望的那样。最后,我们需要在初始化时在地图中注册类型。
所以我的界面最终看起来像这样:
%module test
%{
#include "test.hh"
#include <vector>
#include <map>
#include <string>
#include <typeindex> // C++11! - see: http://stackoverflow.com/a/9859605/168175
%}
%include <std_vector.i>
%{
namespace {
// Internal only, store the type mappings
std::map<std::type_index, swig_type_info*> aheirarchy;
}
namespace swig {
// Forward declare traits, the fragments they're from won't be there yet
template <class Type> struct traits_from_ptr;
template <class Type>
inline swig_type_info *type_info();
template <> struct traits_from_ptr<A> {
static PyObject *from(A *val, int owner = 0) {
auto ty = aheirarchy[typeid(*val)];
if (!ty) {
// if there's nothing in the map, do what SWIG would have done
ty = type_info<A>();
}
return SWIG_NewPointerObj(val, ty, owner);
}
};
}
%}
%template(AList) std::vector<A*>;
%inline %{
const std::vector<A*>& getAllObjects() {
// Demo only
static auto ret = std::vector<A*>{new A, new B, new C, new C, new B};
return ret;
}
%}
%include "test.hh"
%init %{
// Register B and C here
aheirarchy[typeid(B)] = SWIG_TypeQuery("B*");
aheirarchy[typeid(C)] = SWIG_TypeQuery("C*");
%}
我写的%inline
函数只是为了说明足以让事情开始的事情。它允许我运行以下测试Python来演示我的解决方案:
from test import getAllObjects, A, B, C
objects = getAllObjects()
for obj in objects:
print obj
if isinstance(obj, B):
print("OK")
elif isinstance(obj, C):
print("OK")
swig3.0 -c++ -python -Wall test.i
g++ -std=c++11 -Wall test_wrap.cxx -o _test.so -shared -I/usr/include/python2.7/ -fPIC
python run.py
<test.A; proxy of <Swig Object of type 'A *' at 0xf7442950> >
<test.B; proxy of <Swig Object of type 'B *' at 0xf7442980> >
OK
<test.C; proxy of <Swig Object of type 'C *' at 0xf7442fb0> >
OK
<test.C; proxy of <Swig Object of type 'C *' at 0xf7442fc8> >
OK
<test.B; proxy of <Swig Object of type 'B *' at 0xf7442f98> >
OK
您将注意到与getAllObjects
的虚拟实现中创建的类型相匹配。
你可以更整洁地做一些事情:
正如我之前所说,这不是解决这个问题的唯一方法,只是最通用的。
答案 1 :(得分:0)
解决此问题的另一种方法是在.i文件中使用宏。
首先,定义一个可用于检查每种类型的宏:
// dcast and pPyObj defined below...
%define %_container_typemap_dispatch(Type)
if (!dcast) {
Type *obj = dynamic_cast<Type *>(*it);
if (obj != NULL) {
dcast = true;
pPyObj = SWIG_NewPointerObj(%as_voidptr(obj), $descriptor(Type *), $owner | %newpointer_flags);
}
}
%enddef
然后,定义函数以映射列表或向量的输出:
// LIST_TYPEMAP macro to create the proper wrappers in std::list<AbstractElement*>
// It works like %factory but inside std::list elements.
// It only works with types, not specific methods like %factory.
// Usage: container_typemap(OutputType, CastType1, CastType2, ...);
%define %container_typemap(ItemType, Types...)
%typemap(out) ItemType {
PyObject *res = PyList_New($1.size());
int i = 0;
for (ItemType::iterator it = $1.begin();
it != $1.end();
++it, ++i)
{
PyObject* pPyObj = NULL;
bool dcast = false;
%formacro(%_container_typemap_dispatch, Types)
if (!dcast)
// couldn't cast to proper type, use abstract type:
pPyObj = SWIG_NewPointerObj(%as_voidptr(*it), $descriptor, $owner | %newpointer_flags);
if (pPyObj != NULL)
PyList_SetItem(res, i, pPyObj);
}
%set_output(res);
}
%enddef
最后,使用container_typemap函数将其应用于您的对象:
%container_typemap(A, B, C);
然后,只要你想为新的矢量/列表添加它,只需调用:
%container_typemap(Base, Derived1, Derived2, Derived3);