返回基于C ++返回类型的继承Python类

时间:2016-09-24 19:02:08

标签: python swig

让我们说我已经包装了我的C ++类FooBar,并且可以通过SWIG生成的模块wrap_py从Python中访问它们:

// C++

class Bar
{
    int i;
    Bar(int i) { this.i = i; }
}

class Foo 
{
public:
    Foo(Bar* bar) { this.bar = bar; }
    Bar* GetBar() { return this.bar; }
private:
    Bar* bar;
}

在Python中,我创建了面向用户的类,它是一个浅层代理,主要添加文档字符串并允许IDE在参数名称上执行制表选项:

// Python
class Bar(wrap_py.Bar):
    '''Some description ...
    Args:
       i (int): ...
    '''
    def __init__(self, i): 
        super(Bar, self).__init__(i)

class Foo(wrap_py.Foo):
    '''Some description ...
    Args:
       bar (instance of Bar): ...
    '''
    def __init__(self, bar): 
        super(Foo, self).__init__(bar)

问题在于Foo.GetBar(),它是从C ++类自动生成的,它返回类型为wrap_py.Bar的swig实例,它没有文档字符串,也没有' t显示参数名称(swig将所有参数公开为*args)。相反,我希望它提供我自己的浅代理Bar

那么,我如何告诉SWIG,自动返回Bar而不是wrap_py.Bar

编辑:理想情况下,这对于返回类型Bar是可能的,而不仅仅是对于具体的函数签名:

%feature("shadow") Foo::GetBar() %{ def bar(*args): result = $action result.__class__ = Bar return result %}

编辑2:我已经提出了以下装饰器,我需要在每个返回SWIG类型的函数/方法前面放置:

def typemap(f):
from functools import wraps @wraps(f) def wrapper(*args, **kwds): typemap = { wrap_py.Bar: Bar, # more types to come... } result = f(*args, **kwds) if isinstance(result, (tuple, list, set)): for r in result: r.__class__ = typemap.get(r.__class__, r.__class__) elif isinstance(result, dict): for k,v in result.items(): k.__class__ = typemap.get(k.__class__, k.__class__) v.__class__ = typemap.get(v.__class__, v.__class__) else: result.__class__ = typemap.get(result.__class__, result.__class__) return result return wrapper
当然,这并不好,并要求遗漏。

1 个答案:

答案 0 :(得分:1)

您提出的两种解决方案都存在问题。请考虑以下测试用例:

b=Bar(1)
b.woof=2
print(b.woof)
g=(Foo(b).GetBar())
print(type(g))
print(g.woof)

在这个例子中,我们希望最终的印刷声明具有相同的价值。属性作为我们创建的原始Bar对象。也就是说,我们不仅期望匹配类型,而且期望它是同一个实例。使用shadow和decorator方法来包装东西时,每次返回相同的底层C ++ Bar实例时,你仍然会创建新的Python对象。

要解决这个问题,你可能想要做的是设置一个字典,将原始的C ++对象1:1映射到Python代理对象上,并在那里使用它返回一个Bar对象。

作为说明这一点的起点,我设置了以下示例。您的C ++中已修复了多个问题并成为了test.hh:

class Bar
{
    int i;
public:
    Bar(int i) { this->i = i; }
};

class Foo 
{
public:
    Foo(Bar* bar) { this->bar = bar; }
    Bar* GetBar() { return this->bar; }
private:
    Bar* bar;
};

我编写了一个test.i SWIG包装器,根据C ++对象的地址扩展Bar以提供__hash__

%module test
%{
#include "test.hh"
%}

%include <stdint.i>
%include "test.hh"

%extend Bar {
  intptr_t __hash__() {
    return reinterpret_cast<intptr_t>($self);
  }
}

然后最后从Python扩展了wrap.py以实现对象映射和实例查找,包括覆盖GetBar以使用此机制:

import test as wrap_py

class Bar(wrap_py.Bar):
    '''Some description ...
    Args:
       i (int): ...
    '''
    def __init__(self, i): 
        super(Bar, self).__init__(i)
        Bar._storenative(self)

    _objs={}

    @classmethod
    def _storenative(cls, o):
        print('Storing: %d' % hash(o))
        cls._objs[hash(o)]=o

    @classmethod
    def _lookup(cls, o):
        print('Lookup: %d' % hash(o))
        return cls._objs.get(hash(o), o)

class Foo(wrap_py.Foo):
    '''Some description ...
    Args:
       bar (instance of Bar): ...
    '''
    def __init__(self, bar): 
        super(Foo, self).__init__(bar)

    def GetBar(self):
        return Bar._lookup(super(Foo, self).GetBar())

if __name__=='__main__':
  b=Bar(1)
  b.woof=2
  print(b.woof)
  g=(Foo(b).GetBar())
  print(type(g))
  print(g.woof)

虽然第一次切割有一些问题。首先,如您所述,我们仍然必须手动覆盖可能返回Bar实例的每个函数以添加额外的查找调用。其次,查找字典可以使Python代理对象比其C ++对应物更长(并且在最坏的情况下错误地将Python Bar代理映射到未被任何Python派生对象实际代理的C ++对象。为了解决后一个问题,我们可以看看弱引用,但也有缺陷(Python对象可能会过早被破坏)。

为了让所有返回Bar实例的方法透明地工作,你可以沿着两条道路走下去:

  1. 在代理类中实现__getattribute__,让它返回一个包装的函数,并根据返回类型进行适当的查找。
  2. 在SWIG中写出一个类似于上面的类型图,但是基于C ++代码而不是基于Python代码的机制。
  3. 要实现#2,您只需编写一个%typemap(out) Bar*,查看这是否是我们第一次在给定地址看到Bar的实例并返回如果之前看到过,则引用同一个对象,否则创建一个新对象。请注意,如果您还没有阻止中间代理对象使其比实际需要更难,则需要使用swig -builtin。所以我们的界面可以简单地变成:

    %module test
    %{
    #include "test.hh"
    #include <map>
    
    namespace {
      typedef std::map<Bar*,PyObject*> proxy_map_t;
      proxy_map_t proxy_map;
    }
    %}
    
    %typemap(out) Bar* {
      assert($1);
      const auto it = proxy_map.find($1);
      if (it != proxy_map.end()) {
        $result = it->second;
      }
      else {
        $result = SWIG_NewPointerObj(SWIG_as_voidptr($1), $1_descriptor, $owner);
        proxy_map.insert(std::make_pair($1, $result));
      }
      Py_INCREF($result);
    }
    
    %include "test.hh"
    

    然后编译并运行未经修改的Python。

    swig3.0 -python -c++ -Wall -builtin test.i
    g++ -shared -o _test.so test_wrap.cxx -Wall -Wextra -fPIC -I/usr/include/python2.7/ -std=c++11
    python wrap.py
    

    还有一个悬而未决的问题是:我们无法查看Bar*个实例何时被删除,因此我们最终会遇到意外回收Python代理对象的情况跨越多个C ++的生命。根据您的目标,您可以在地图中使用弱引用来解决此问题,或者您可以(ab)使用operator new()来挂钩Bar*实例的创建。