返回基于C ++返回类型的继承的Python类
比方说,我已经封装了我的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)
问题在于,从C ++类自动生成的Foo.GetBar()
返回wrap_py.Bar
类型的swig实例,该实例没有文档字符串,也没有显示参数名称(swig公开了所有参数as *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 wrapperdef 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)
在那个例子中,我们期望最终的打印语句对于'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包装器,它扩展Bar以提供基于C ++对象地址的__hash__
:
%module test
%{
#include "test.hh"
%}
%include <stdint.i>
%include "test.hh"
%extend Bar {
intptr_t __hash__() {
return reinterpret_cast<intptr_t>($self);
}
}
最后,wrap.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对象可能会过早地被破坏)。
为了让这个对所有返回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*
实例何时被删除,所以我们最终可能会意外地在多个C ++生命周期中意外回收我们的Python代理对象。 取决于你想要做什么,你可以在地图内部使用弱引用来解决这个问题,或者你可以(ab)使用operator new()
钩住Bar*
实例的创建。
上一篇: Return inherited Python class based on C++ return types