返回继承了基于C++的返回类型
比方说,我都包裹着我的C++类Foo
和Bar
并可通过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
有既您已经提出的解决方案的问题。请看下面的测试案例:
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的对象可以提前而不是被摧毁)
为了得到这个为透明地运行所有方法,返回酒吧情况下,你可以沿着两条路之一:
- 在你的代理类中实现
__getattribute__
,让它返回一个函数,它根据返回类型。 - 在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*
实例的创建。
不幸的是,我不能使用-builtin,因为我需要覆盖__radd__之类的东西。我知道去代理路由,每个Swig调用都会给我一个新的Python实例,并且我已经注意到所有的比较等都是用C++完成的(通过__eq__和__hash__)。不过,我希望有一些更优雅的方式...... -/ –
@wr -builtin不会中断'__radd__'等等,您需要使用slots机制来这样做:http:// www。 swig.org/Doc3.0/Python。html#Python_builtin_overloads – Flexo
我觉得你可能会让整个事情变得比它需要的复杂得多 - 如果你不使用-builtin – Flexo
您可以添加一个最小的完整示例来显示失败的位置?我不太了解你提出的问题,我认为无论如何,这可能是一个简单的解决方案。 – Flexo
绝对有可能,但我不确定这是否是正确的解决方案。我会尽量在本周晚些时候写一个完整的答案。 – Flexo
@Flexo,对此有何更新? –