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

比方说,我已经封装了我的C ++类FooBar并且可以通过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 wrapper
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对象相同的值。 也就是说,我们期望不仅类型匹配,而且它也是同一个实例。 使用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__ ,让它返回一个包装的函数,并根据返回类型进行适当的查找。
  • 在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代理对象。 取决于你想要做什么,你可以在地图内部使用弱引用来解决这个问题,或者你可以(ab)使用operator new()钩住Bar*实例的创建。

    链接地址: http://www.djcxy.com/p/40933.html

    上一篇: Return inherited Python class based on C++ return types

    下一篇: Why does "not(True) in [False, True]" return False?