Return inherited Python class based on C++ return types
假设我已经封装了我的C++类EDOCX1,0和EDCOX1,1,并且可以通过SWIG生成模块EDOCX1 2来访问Python。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // 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中,我创建了一个面向用户的类,它是一个浅代理,主要添加docstrings,并允许IDE在参数名上完成制表符:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // 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++类自动生成的EDCOX1(3),返回EDCOX1(4)类型的SWIG实例,它不具有文档字符串,也不显示参数名称(SWIG将所有参数暴露为EDCOX1×5)。相反,我希望它能提供我自己的肤浅代理
那么,我怎样才能告诉Swig,自动返回
编辑:理想情况下,这对于返回类型
%feature("shadow") Foo::GetBar() %{
def bar(*args):
result = $action
result.__class__ = Bar
return result
%}
编辑2:我想出了下面的装饰器,我需要把它放在返回swig类型的每个函数/方法前面:
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 2 3 4 5 6 | b=Bar(1) b.woof=2 print(b.woof) g=(Foo(b).GetBar()) print(type(g)) print(g.woof) |
在这个例子中,我们期望最终的print语句的"woof"属性值与我们创建的原始bar对象的值相同。也就是说,我们不仅希望类型匹配,而且希望它是相同的实例。使用阴影和装饰器的方法来包装事物,每次返回相同的基础C++条形码实例时,仍在创建新的Python对象。
为了解决这个问题,您可能需要设置一个字典,将原始C++对象1:1映射到Python代理对象上,并使用它返回的条形码对象。
作为说明这一点的起点,我建立了以下示例。你的C++有固定的多个问题,变成了Test.H.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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; }; |
我编写了一个测试。我用Cu+对象的地址,将扩展条包装器提供EDOCX1 0。
1 2 3 4 5 6 7 8 9 10 11 12 13 | %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扩展来实现对象映射和实例查找,包括重写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | 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实例的所有方法,您可以沿着以下两条道路之一进行操作:
要实现2,您只需要编写一个单独的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | %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编译和运行。
1 2 3 | 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 |
仍然有一个突出的问题:当EDCOX1的5个实例被删除时,我们无法看到,所以我们最终会遇到一个情况,我们在多个C++的生命周期中意外地回收了Python代理对象。根据您的目标,您可以在地图中使用弱引用来解决这个问题,或者(ab)使用