SwigPyIterator 值始终为基础 class
SwigPyIterator value is always base class
我在 C++ 中有 类 A、B、C。 B 和 C 派生自 A。我在 B 和 C 的 C++ returns 向量中具有函数:如 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,如何解决问题?
您需要 一些东西 才能继续,而不仅仅是类型层次结构。通常在 Python/SWIG 场景中,以下其中一项就足够了:
- 基础class中的一个
virtual
函数(即RTTI)
- 一些成员变量或函数以某种方式标识给定实例的最派生类型(例如,C 中的常见模式是将结构的第一个字段包含为某种类型标识符)。
- 对象创建时的某种挂钩,例如,如果您知道所有实例都将 Python created/owned
我假设第一种类型就足够了,但即使对于其他情况也不难适应。
为了说明这一点,我编写了以下头文件:
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
迭代而不只是 return 是单个 A*
的函数会使问题稍微复杂一些。如果我们只是使用函数的 return 值,我们会写一个 typemap(out)
。然而,在 std::vector<A*>
的情况下,可以自定义迭代的行为 returning 并插入额外的代码以确保 Python 知道派生类型而不仅仅是基类. SWIG 有一个类型特征机制,大多数标准容器都使用该机制来帮助它们进行常见用途(例如迭代),而不会出现过多的重复。 (作为参考,我认为在 std_common.i 中)。
所以基本计划是挂钩到迭代过程的输出(SwigPyIterator
,在这种情况下实现为 SwigPyIteratorClosed_T
),使用 SWIG 引入的特征类型来自定义它。在该钩子内,我们将使用 typeid
在 std::map
中动态查找类型,而不是盲目地使用 A*
的 SWIG 类型信息。该映射在模块内部维护。如果我们在该映射中找到任何东西,我们将把它用于 return 更派生的 Python 对象,正如 Python 程序员所期望的那样。最后,我们需要在初始化时在映射中注册类型。
所以我的界面最终看起来像这样:
%module test
%{
#include "test.hh"
#include <vector>
#include <map>
#include <string>
#include <typeindex> // C++11! - see:
%}
%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
的虚拟实现中创建的类型相匹配。
你可以做一些更巧妙的事情:
- 添加用于注册类型的宏。 (或者以其他方式自动执行)
- 如果需要,为常规 returning 对象添加类型映射。
正如我之前指出的,这不是解决此问题的唯一方法,只是最通用的方法。
解决此问题的另一种方法是在 .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(.size());
int i = 0;
for (ItemType::iterator it = .begin();
it != .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);
然后,任何时候你想为新的 vector/list 添加这个,只需调用:
%container_typemap(Base, Derived1, Derived2, Derived3);
我在 C++ 中有 类 A、B、C。 B 和 C 派生自 A。我在 B 和 C 的 C++ returns 向量中具有函数:如 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,如何解决问题?
您需要 一些东西 才能继续,而不仅仅是类型层次结构。通常在 Python/SWIG 场景中,以下其中一项就足够了:
- 基础class中的一个
virtual
函数(即RTTI) - 一些成员变量或函数以某种方式标识给定实例的最派生类型(例如,C 中的常见模式是将结构的第一个字段包含为某种类型标识符)。
- 对象创建时的某种挂钩,例如,如果您知道所有实例都将 Python created/owned
我假设第一种类型就足够了,但即使对于其他情况也不难适应。
为了说明这一点,我编写了以下头文件:
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
迭代而不只是 return 是单个 A*
的函数会使问题稍微复杂一些。如果我们只是使用函数的 return 值,我们会写一个 typemap(out)
。然而,在 std::vector<A*>
的情况下,可以自定义迭代的行为 returning 并插入额外的代码以确保 Python 知道派生类型而不仅仅是基类. SWIG 有一个类型特征机制,大多数标准容器都使用该机制来帮助它们进行常见用途(例如迭代),而不会出现过多的重复。 (作为参考,我认为在 std_common.i 中)。
所以基本计划是挂钩到迭代过程的输出(SwigPyIterator
,在这种情况下实现为 SwigPyIteratorClosed_T
),使用 SWIG 引入的特征类型来自定义它。在该钩子内,我们将使用 typeid
在 std::map
中动态查找类型,而不是盲目地使用 A*
的 SWIG 类型信息。该映射在模块内部维护。如果我们在该映射中找到任何东西,我们将把它用于 return 更派生的 Python 对象,正如 Python 程序员所期望的那样。最后,我们需要在初始化时在映射中注册类型。
所以我的界面最终看起来像这样:
%module test
%{
#include "test.hh"
#include <vector>
#include <map>
#include <string>
#include <typeindex> // C++11! - see:
%}
%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
的虚拟实现中创建的类型相匹配。
你可以做一些更巧妙的事情:
- 添加用于注册类型的宏。 (或者以其他方式自动执行)
- 如果需要,为常规 returning 对象添加类型映射。
正如我之前指出的,这不是解决此问题的唯一方法,只是最通用的方法。
解决此问题的另一种方法是在 .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(.size());
int i = 0;
for (ItemType::iterator it = .begin();
it != .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);
然后,任何时候你想为新的 vector/list 添加这个,只需调用:
%container_typemap(Base, Derived1, Derived2, Derived3);