我正在尝试使用swig为一些C++代码创建python绑定。我似乎在尝试从一些访问器函数中创建python属性时遇到了问题,这些函数是用于类似以下方法的方法:
class Player {
public:
void entity(Entity* entity);
Entity* entity() const;
};
我尝试使用Python的property函数创建属性,但似乎Swig生成的包装类与它不兼容,至少在设置器方面如此。
您如何使用Swig创建属性?
我正在尝试使用swig为一些C++代码创建python绑定。我似乎在尝试从一些访问器函数中创建python属性时遇到了问题,这些函数是用于类似以下方法的方法:
class Player {
public:
void entity(Entity* entity);
Entity* entity() const;
};
我尝试使用Python的property函数创建属性,但似乎Swig生成的包装类与它不兼容,至少在设置器方面如此。
您如何使用Swig创建属性?
有一种简单的方法可以使用 SWIG 从方法中创建 Python 属性。
假设有以下 C++ 代码 Example.h:
C++ 头文件
class Example{
public:
void SetX(int x);
int GetX() const;
};
让我们将这个setter和getter转换为Python的property 'x'。诀窍在于.i文件中。我们添加一些"swiggy"内联Python代码(使用%pythoncode),该代码插入到生成的Python类的主体中。
Swig包装Example.i示例:
%module example
%{
#include "example.h"
%}
class Example{
public:
void SetX(int x);
int GetX() const;
%pythoncode %{
__swig_getmethods__["x"] = GetX
__swig_setmethods__["x"] = SetX
if _newclass: x = property(GetX, SetX)
%}
};
检查以下Python代码:
Python测试代码
import example
test = example.Example()
test.x = 5
print "Ha ha ha! It works! X = ", repr(test.x)
就是这样了!
没有必要重新编写类定义。感谢 Joshua 的建议,可以使用 SWIG 指令%extend ClassName {}。
Swig 封装 Example.i 示例代码:
%module example
%{
#include "example.h"
%}
%extend Example{
%pythoncode %{
__swig_getmethods__["x"] = GetX
__swig_setmethods__["x"] = SetX
if _newclass: x = property(GetX, SetX)
%}
};
正如你所看到的,即使进行了转换,test.GetX() 和 test.SetX() 仍然存在。可以通过以下方式将它们隐藏起来:
a) 使用 %rename 重命名函数,在函数名前加上 '_',从而使方法对于 Python “私有”。在 SWIG 接口文件 Example.i 中实现。
...
class Example{
%rename(_SetX) SetX(int);
%rename(_GetX) GetX();
...
为了将此类转换为其他不需要这些“_”的语言,可以将%rename放在某个分离的位置上保存。
b) 或者可以使用%feature("shadow")来实现。
为什么我们要使用SWIG来将方法转换为属性?正如所说,SWIG自私地覆盖了_setattr_,因此必须使用_swig_getmethods_和_swig_setmethods_来注册函数并保持SWIG的方式。
上面列出的方法,特别是使用PropertyVoodoo的方法...这就像烧房子来煎蛋。它还破坏了类的布局,因为必须创建继承类来从C++方法中制作python属性。我的意思是,如果类Cow返回类Milk,并且继承类是MilkWithProperties(Milk),那么如何使Cow产生MilkWithProperties?
这种方法允许您:
更新 在更新版本的SWIG中放弃了_swig_property,因此可以使用property。它与旧版本的SWIG相同。我已经更新了文章。
在SWIG Lib文件夹中有一个名为 "attributes.i" 的文件,该文件未在文档中讨论,但包含内联文档。
你只需要将以下行添加到你的接口文件即可。
%include <attributes.i>
然后您会收到一些宏(例如%attribute),用于从现有方法中定义属性。
来自attributes.i文件文档的摘录:
以下宏将一对set/get方法转换为“本地”属性。当您拥有一对与原始类型类似的get/set方法时,请使用%attribute:
%attribute(A, int, a, get_a, set_a);
struct A
{
int get_a() const;
void set_a(int aa);
};
%include <attribute.i>
。 - Homar#ifndef EXAMPLE_H
#define EXAMPLE_H
class Math {
public:
int pi() const {
return this->_pi;
}
void pi(int pi) {
this->_pi = pi;
}
private:
int _pi;
};
#endif
%module example
%{
#define SWIG_FILE_WITH_INIT
#include "example.h"
%}
[essentially example.h repeated again]
#include "example.h"
class PropertyVoodoo(type):
"""A metaclass. Initializes when the *class* is initialized, not
the object. Therefore, we are free to muck around the class
methods and, specifically, descriptors."""
def __init__(cls, *a):
# OK, so the list of C++ properties using the style described
# in the OP is stored in a __properties__ magic variable on
# the class.
for prop in cls.__properties__:
# Get accessor.
def fget(self):
# Get the SWIG class using super. We have to use super
# because the only information we're working off of is
# the class object itself (cls). This is not the most
# robust way of doing things but works when the SWIG
# class is the only superclass.
s = super(cls, self)
# Now get the C++ method and call its operator().
return getattr(s, prop)()
# Set accessor.
def fset(self, value):
# Same as above.
s = super(cls, self)
# Call its overloaded operator(int value) to set it.
return getattr(s, prop)(value)
# Properties in Python are descriptors, which are in turn
# static variables on the class. So, here we create the
# static variable and set it to the property.
setattr(cls, prop, property(fget=fget, fset=fset))
# type() needs the additional arguments we didn't use to do
# inheritance. (Parent classes are passed in as arguments as
# part of the metaclass protocol.) Usually a = [<some swig
# class>] right now.
super(PropertyVoodoo, cls).__init__(*a)
# One more piece of work: SWIG selfishly overrides
# __setattr__. Normal Python classes use object.__setattr__,
# so that's what we use here. It's not really important whose
# __setattr__ we use as long as we skip the SWIG class in the
# inheritance chain because SWIG's __setattr__ will skip the
# property we just created.
def __setattr__(self, name, value):
# Only do this for the properties listed.
if name in cls.__properties__:
object.__setattr__(self, name, value)
else:
# Same as above.
s = super(cls, self)
s.__setattr__(name, value)
# Note that __setattr__ is supposed to be an instance method,
# hence the self. Simply assigning it to the class attribute
# will ensure it's an instance method; that is, it will *not*
# turn into a static/classmethod magically.
cls.__setattr__ = __setattr__
import example
from util import PropertyVoodoo
class Math(example.Math):
__properties__ = ['pi']
__metaclass__ = PropertyVoodoo
m = Math()
print m.pi
m.pi = 1024
print m.pi
m.pi = 10000
print m.pi
# (NOTE: Hao's comments removed for brevity)
class PropertyVoodoo(type):
def __init__(cls, *a):
for prop in cls.__properties__:
def fget(self, _prop = str(prop)):
s = super(cls, self)
return getattr(s, _prop)()
def fset(self, value, _prop = str(prop)):
s = super(cls, self)
return getattr(s, _prop)(value)
setattr(cls, prop, property(fget=fget, fset=fset))
super(PropertyVoodoo, cls).__init__(*a)
def __setattr__(self, name, value):
if name in cls.__properties__:
object.__setattr__(self, name, value)
else:
s = super(cls, self)
s.__setattr__(name, value)
cls.__setattr__ = __setattr__
来自http://www.swig.org/Doc2.0/SWIGDocumentation.html#SWIG_adding_member_functions:
%extend指令的一个鲜为人知的特性是它也可以用于添加合成属性或修改现有数据属性的行为。例如,假设您想要将magnitude作为Vector的只读属性而不是方法。
因此,在您的示例中,以下内容应该有效:
%extend Player {
Entity entity;
}
%{
Entity* Player_entity_get(Player* p) {
return p->get_entity();
}
void Player_entityProp_set(Player* p, Entity* e) {
p->set_entity(e);
}
%}
我也遇到了同样的问题,使用%pythoncode的建议对我很有帮助。这是我所做的:
class Foo {
// ...
std::string get_name();
bool set_name(const std::string & name);
};
在包装器中:
%include "foo.h"
%pythoncode %{
def RaiseExceptionOnFailure(mutator):
def mutator(self, v):
if not mutator(self, v):
raise ValueError("cannot set property")
return wrapper
Foo.name = property(Foo.get_name, RaiseExceptionOnFailure(Foo.set_name))
%}
_newclass
是用来做什么的? - Dave