SwigPyIterator值始终是基类

时间:2016-08-29 09:01:38

标签: python c++ swig

我在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.如何解决问题?

2 个答案:

答案 0 :(得分:3)

除了类型层次结构之外,还需要某些更多内容。通常在Python / SWIG场景中,下列之一就足够了:

  1. 基类中的virtual函数(即RTTI)
  2. 以某种方式识别给定实例的派生类型最多的成员变量或函数(例如,C中的常见模式是包含结构的第一个字段是某种类型标识符)。
  3. 在对象创建时的某种钩子,例如,如果你知道所有实例都是Python创建/拥有的
  4. 我正在努力假设第一种类型已经足够,但即使对于其他情况,它也不难以适应。

    为了说明这一点,我写了以下头文件:

    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类型信息,而是使用typeidstd::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. 添加用于注册类型的宏。 (或者以其他方式自动完成)
    2. 根据需要添加用于定期返回对象的字体图。
    3. 正如我之前所说,这不是解决这个问题的唯一方法,只是最通用的。

答案 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);