让我们说我已经包装了我的C ++类Foo
和Bar
,并且可以通过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
答案 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实例的方法透明地工作,你可以沿着两条道路走下去:
__getattribute__
,让它返回一个包装的函数,并根据返回类型进行适当的查找。要实现#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*
实例的创建。