当前位置: 首页 > 知识库问答 >
问题:

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

东郭骁
2023-03-14

假设我已经包装了我的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中,我创建了面向用户的类,它是一个浅层代理,主要添加docstring,并允许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。从C类自动生成的GetBar(),返回类型为wrap\u py的swig实例。条形图,它没有docstring,也不显示参数名称(swig将所有参数公开为*args)。相反,我希望它提供我自己的浅代理

那么,我如何告诉SWIG自动返回Bar,而不是裸wrap\u py。条形码?

编辑:理想情况下,这对于返回类型Bar是可能的,而不仅仅是对于具体的函数签名:

编辑2:我提出了以下装饰器,我需要将其放在每个返回SWIG类型的函数/方法前面:

def typemap(f):
从functools导入wrapps@wrapps(f)def wrapper(*args,**kwds):typemap={wrap_py.Bar:Bar,#更多类型将出现…}result=f(*参数,**kwds),如果是instance(result,(tuple,list,set)):对于result中的r:r.。\uuuu class\uuuu=typemap。获取(r。items():k.。\uuuuuu类\uuuuu=typemap。获取(k。获取(v__类=类型映射。get(result.\uuuuu class\uuuuu,result.\uuuuu class\uuuuuu)返回结果返回包装器
当然,这不好,需要省略。


共有1个答案

袁奇文
2023-03-14

你提出的两种解决方案都有问题。考虑下面的测试用例:

b=Bar(1)
b.woof=2
print(b.woof)
g=(Foo(b).GetBar())
print(type(g))
print(g.woof)

在该示例中,我们希望最终的print语句的“woof”属性值与我们创建的原始条对象的值相同。也就是说,我们不仅希望类型匹配,而且希望它是相同的实例。使用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);
  }
}

然后最后包装。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对象上,而该C对象实际上没有被任何Python派生对象代理。要解决后一个问题,我们可以查看弱引用,但这也有缺陷(Python对象可能会过早地被销毁)。

要使其对所有返回条实例的方法透明地工作,您可以选择以下两种方法之一:

  1. 在代理类中实现\uuuu getattribute\uuuu,让它返回一个函数,该函数根据返回类型进行适当的包装和查找
  2. 在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*实例的创建。

 类似资料:
  • 下面的代码来自一个名为ButterKnife的Android库。我正在弄清楚它是怎么工作的。 我试图重新创建此函数的行为: 和用法: 但是异常并非永远不会被捕获,而是在调用方法时在行中抛出。为什么? 还有,这到底是如何工作的?该方法如何知道要转换到什么?

  • 问题内容: 当我尝试编译时,它给了我错误 我应该如何解决这个问题? 问题答案: 该错误是由于以下事实导致的:调用将是不明确的- 应该调用两种方法中的哪一种?从JLS§8.4.2开始: 在类中声明两个具有重写等效签名的方法是编译时错误。 方法的返回类型不是其签名的一部分,因此根据上述说明,您将收到错误。 假设您不能简单地重命名冲突的方法,在这种情况下就不能使用继承,并且需要使用诸如compositi

  • 问题内容: 在JavaScript中,每个对象同时是一个实例和一个类。要进行继承,可以将任何对象实例用作原型。 在Python,C ++等中,有类和实例作为单独的概念。为了进行继承,您必须使用基类创建一个新类,然后可以使用该新类来生成派生实例。 为什么JavaScript朝这个方向发展(基于原型的面向对象)?与传统的基于类的OO相比,基于原型的OO有哪些优点和缺点? 问题答案: 这里大约有一百个术

  • 我试着写一个小函数,它接受两个列表,并根据另一个列表的元素对一个进行排序。所以类似于: 将产生一个排序列表。 然而,可能是一个不同的列表,比如整数、浮点数或其他列表。理想情况下,我希望我的程序能够获取我抛出的任何列表,根据

  • 我可以看到它不工作,因为我尝试了它。我只是无法解释为什么一定要这样。 第二个方法来自一个类,该类是实现第一个getValue()方法的类的子类。 为什么同名不足以覆盖该方法?我想到的唯一论点是,它将反对“是一个”关系,因为用A扩展的第二个方法的类必须具有与A相同的能力,如果你重写返回类型,你就打破了那个法律,对吧?

  • 我想写一个函数返回一个数组,其所有子数组的长度必须为2。例如,返回将是。 我定义: (1); 和 我觉得(1)太复杂了。有更简单的吗?