python继承类return_返回基于C ++返回类型的继承的Python类

假设我已经包装了C ++类Foo,Bar并且可以通过SWIG生成的模块从Python中很好地访问它们wrap_py:

// C ++

班级酒吧{我Bar(int i){this.i = i;}}

Foo类{上市:Foo(Bar * bar){this.bar = bar;}Bar * GetBar(){返回this.bar;}私人的:酒吧*酒吧;}

在Python中,我创建了一个面向用户的类,它是一个浅层代理,主要添加文档字符串并允许IDE对参数名称进行制表符补全:

// PythonBar类(wrap_py.Bar):'''一些描述...精氨酸:我(int):...'''def __init __(self,i):超级(酒吧,自我).__ init __(i)

类Foo(wrap_py.Foo):'''一些描述...精氨酸:酒吧(酒吧的实例):...'''def __init __(self,bar):超级(Foo,self).__ init __(bar)

问题是Foo.GetBar()从C ++类自动生成的,返回类型为的swig实例wrap_py.Bar,该实例没有文档字符串,也没有显示参数名称(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当然,这不是很好,需要忽略。

解决方案

您提出的两种解决方案都存在问题。考虑以下测试案例:

b=Bar(1)

b.woof=2

print(b.woof)

g=(Foo(b).GetBar())

print(type(g))

print(g.woof)

在该示例中,我们希望最终的打印语句的“ woof”属性值与我们创建的原始Bar对象的值相同。也就是说,我们不仅希望类型匹配,而且希望它是同一实例。通过阴影和装饰器两种方法来包装东西,每次返回相同的基础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以__hash__根据C ++对象的地址提供:

%module test

%{

#include "test.hh"

%}

%include

%include "test.hh"

%extend Bar {

intptr_t __hash__() {

return reinterpret_cast($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)

There are a few issues with this first cut though. Firstly as you noted we still have to manually override each and every function that could return an instance of Bar to add the extra lookup call. Secondly the lookup dictionary can cause the Python proxy objects to outlive their C++ counterparts (and in the worst case incorrectly map a Python Bar proxy onto a C++ object that is not really proxied by any Python derived object. To solve the latter problem we could look at weak references, but that too has flaws (the Python objects can get destroyed prematurely instead).

To get this to work transparently for all methods which return Bar instances you could go down one of two roads:

Implement __getattribute__ in your proxy class, have it return a function that wrapped and did lookups appropriately based on the return type.

Write an out typemap in SWIG that does something similar to the above, but based on C++ code not Python code for the mechanics.

To implement #2 you'll just need to write a single %typemap(out) Bar* which looks to see if this is the first time we've seen an instance of Bar at a given address and returns a reference to the same object if was seen before, or creates a new one otherwise. Note that you'll need to use swig -builtin if you aren't already to prevent the intermediate proxy object making this harder than it needed to be. So our interface can simply become:

%module test

%{

#include "test.hh"

#include

namespace {

typedef std::map 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"

Which then compiles and runs with the Python unmodified from above.

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

There's one outstanding issue with this still: we don't get to see when Bar* instances get deleted, so we can end up in a situation where we accidentally recycle our Python proxy objects across the life of multiple C++ ones. Depending on what you're aiming to do you could use weak reference inside the map to work around this, or you could (ab)use operator new() to hook the creation of Bar* instances.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值