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 场景中,以下其中一项就足够了:

  1. 基础class中的一个virtual函数(即RTTI)
  2. 一些成员变量或函数以某种方式标识给定实例的最派生类型(例如,C 中的常见模式是将结构的第一个字段包含为某种类型标识符)。
  3. 对象创建时的某种挂钩,例如,如果您知道所有实例都将 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 引入的特征类型来自定义它。在该钩子内,我们将使用 typeidstd::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 的虚拟实现中创建的类型相匹配。

你可以做一些更巧妙的事情:

  1. 添加用于注册类型的宏。 (或者以其他方式自动执行)
  2. 如果需要,为常规 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);