2016-09-24 72 views
0

比方說,我都包裹着我的C++類FooBar並可通過SWIG生成模塊wrap_py從Python中訪問它們就好了Python類:返回繼承了基於C++的返回類型

// 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) 

的問題是這裏Foo.GetBar(),W HICH被自動地從C++類生成,返回wrap_py.Bar類型,它不具有文檔字符串的痛飲實例,也沒有示出的參數名稱(大口暴露所有參數*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
當然,這是不好的,並調用遺漏。

+0

您可以添加一個最小的完整示例來顯示失敗的位置?我不太瞭解你提出的問題,我認爲無論如何,這可能是一個簡單的解決方案。 – Flexo

+0

絕對有可能,但我不確定這是否是正確的解決方案。我會盡量在本週晚些時候寫一個完整的答案。 – Flexo

+0

@Flexo,對此有何更新? –

回答

1

有既您已經提出的解決方案的問題。請看下面的測試案例:

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

在那個例子中,我們期待最終的打印語句有作爲我們創建做了原有的Bar對象的「汪汪」屬性的值相同。也就是說,我們期望不僅類型匹配,而且它也是同一個實例。使用shadow和decorator方法來包裝事物,每次返回相同的底層C++ Bar實例時,您仍然在創建新的Python對象。

要解決什麼你可能想要做的是建立++對象1字典映射原來的C:1到Python的代理對象,並使用處處有一個酒吧對象返回。

作爲出發點來說明這個我已經設置了下面的例子。你的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; 
}; 

我寫的擴展欄基於對C++對象的地址,以提供一個__hash__夜風test.i包裝:

%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++對象上,而這些對象並不是真正由任何Python派生對象代理的。爲了解決後面的問題,我們可以看看在弱引用,但也有缺陷(Python的對象可以提前而不是被摧毀)

爲了得到這個爲透明地運行所有方法,返回酒吧情況下,你可以沿着兩條路之一:

  1. 在你的代理類中實現__getattribute__,讓它返回一個函數,它根據返回類型。
  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代理對象++那些。根據你的目標,你可以在地圖內部使用弱引用來解決這個問題,或者你可以使用operator new()鉤住Bar*實例的創建。

+0

不幸的是,我不能使用-builtin,因爲我需要覆蓋__radd__之類的東西。我知道去代理路由,每個Swig調用都會給我一個新的Python實例,並且我已經注意到所有的比較等都是用C++完成的(通過__eq__和__hash__)。不過,我希望有一些更優雅的方式...... -/ –

+0

@wr -builtin不會中斷'__radd__'等等,您需要使用slots機制來這樣做:http:// www。 swig.org/Doc3.0/Python。html#Python_builtin_overloads – Flexo

+0

我覺得你可能會讓整個事情變得比它需要的複雜得多 - 如果你不使用-builtin – Flexo