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

问题描述:

比方说,我都包裹着我的C++类FooBar并可通过SWIG生成模块wrap_py从Python中访问它们就好了Python类:返回继承了基于C++的返回类型

// 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(),W HICH被自动地从C++类生成,返回wrap_py.Bar类型,它不具有文档字符串的痛饮实例,也没有示出的参数名称(大口暴露所有参数*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
当然,这是不好的,并调用遗漏。

+0

您可以添加一个最小的完整示例来显示失败的位置?我不太了解你提出的问题,我认为无论如何,这可能是一个简单的解决方案。 – Flexo

+0

绝对有可能,但我不确定这是否是正确的解决方案。我会尽量在本周晚些时候写一个完整的答案。 – Flexo

+0

@Flexo,对此有何更新? –

有既您已经提出的解决方案的问题。请看下面的测试案例:

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对象。

要解决什么你可能想要做的是建立++对象1字典映射原来的C:1到Python的代理对象,并使用处处有一个酒吧对象返回。

作为出发点来说明这个我已经设置了下面的例子。你的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; 
}; 

我写的扩展栏基于对C++对象的地址,以提供一个__hash__夜风test.i包装:

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

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

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

然后最后包装。PY是从你的Python扩展以实现对象映射和实例的查找,包括覆盖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代理映射到C++对象上,而这些对象并不是真正由任何Python派生对象代理的。为了解决后面的问题,我们可以看看在弱引用,但也有缺陷(Python的对象可以提前而不是被摧毁)

为了得到这个为透明地运行所有方法,返回酒吧情况下,你可以沿着两条路之一:

  1. 在你的代理类中实现__getattribute__,让它返回一个函数,它根据返回类型。
  2. 在SWIG中写出一个类似于上面的类型图,但是基于C++代码而不是基于Python代码的机制。

要实现#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*情况下被删除,因此我们可以在一个情况下结束,我们意外地跨越多个C的寿命循环利用我们的Python代理对象++那些。根据你的目标,你可以在地图内部使用弱引用来解决这个问题,或者你可以使用operator new()钩住Bar*实例的创建。

+0

不幸的是,我不能使用-builtin,因为我需要覆盖__radd__之类的东西。我知道去代理路由,每个Swig调用都会给我一个新的Python实例,并且我已经注意到所有的比较等都是用C++完成的(通过__eq__和__hash__)。不过,我希望有一些更优雅的方式...... -/ –

+0

@wr -builtin不会中断'__radd__'等等,您需要使用slots机制来这样做:http:// www。 swig.org/Doc3.0/Python。html#Python_builtin_overloads – Flexo

+0

我觉得你可能会让整个事情变得比它需要的复杂得多 - 如果你不使用-builtin – Flexo