20

I am attempting to create python bindings for some C++ code using swig. I seem have run into a problem trying to create python properties from some accessor functions I have for methods like the following:

class Player {
public:
  void entity(Entity* entity);
  Entity* entity() const;
};

I tried creating a property using the python property function but it seems that the wrapper classes swig generates are not compatible with it at least for setters.

How do you create properties using swig?

fuzzy-waffle
  • 830
  • 5
  • 11

6 Answers6

35

There is an easy way to make python properties from methods with swig.
Suppose C++ code Example.h:

C++ header

class Example{
    public:
      void SetX(int x);
      int  GetX() const;
    };

Lets convert this setter and getter to python propery 'x'. The trick is in .i file. We add some "swiggy" inline python code (with %pythoncode) that is inserted in a body of a resulting python class (in the auto-generated python code).

Swig wrapping 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)
      %}
    };

Check the python code:

python test code

import example

test = example.Example()
test.x = 5
print "Ha ha ha! It works! X = ", repr(test.x)

That is it!



Make it simplier!

There is no need to rewrite a class definition. Thanks to Joshua advice, one could use SWIG directive %extend ClassName { }.

Swig wrapping Example.i

%module example
%{
     #include "example.h"
%}

%extend Example{
      %pythoncode %{
         __swig_getmethods__["x"] = GetX
         __swig_setmethods__["x"] = SetX
         if _newclass: x = property(GetX, SetX)
      %}
    };

Hiding setter and getter functions

As one may see, test.GetX() and test.SetX() are still in place after conversion. One can hide them by:

a) Rename functions by using %rename, add '_' in the beginning thus making methods "private" for python. In the a SWIG interface .i Example.i

...
class Example{
   %rename(_SetX) SetX(int);
   %rename(_GetX) GetX();
...

(%rename may be placed in some separated place to save the possibility to convert this class to other languages, which don't need these '_')

b) or one can play with %feature("shadow")

Why is it so?

Why do we have to use such things to convert methods to a property by using SWIG? As it was said, SWIG selfishly overrides _setattr_, so one have to use _swig_getmethods_ and _swig_setmethods_ to register functions and stay in the swig way.

Why may one prefer this way?

The methods listed above, especially with PropertyVoodoo are... It is like burning the house to fry an egg. Also it breaks the classes layout, as one have to create inherited classes to make python properties from C++ methods. I mean if class Cow returns class Milk and the inherited class is MilkWithProperties(Milk), how to make Cow to produce MilkWithProperties?

This approach allows one to:

  1. explicitly control what C++ methods to convert to python properties
  2. conversion rules are located in swig interface(*.i) files, the place where they are supposed to be
  3. one resulting autogenerated .py file
  4. stay in the swig syntax of insertions in swig generated .py file
  5. %pythoncode is ignored if one wraps library to other languages

Update In a newer version SWIG abandoned _swig_property so just use property. It works with old version of swig the same. I've changed the post.

MajesticRa
  • 13,770
  • 12
  • 63
  • 77
  • 3
    thank you! This seems to work beautifully. In case others find it helpful, remember that with swig you can use%extend ClassName { } to add methods to the class from within the interface file when the class is defined in separate header files. I believe that this method will even work when you overload your C++ getter and setter with the same name. – Joshua Jan 22 '11 at 00:26
  • I will have to try your answer out the next time I need to do some swig and python. I this is more of the solution I was looking for if it does indeed work as advertised. – fuzzy-waffle Jan 31 '11 at 21:35
  • what does the `_newclass` do? – Dave Apr 24 '17 at 13:00
  • As I recall it is something connected with old pythons compatibility (like from 2.4 to 2.6). Something like properties in its current form were introduced in python 2.6 (python 2.4 had self made libraries with properties). So if you working with modern pythons (2.7, 3.x) you can skip it. I could be wrong though. – MajesticRa Apr 25 '17 at 00:54
22

Use Attributes.i

In the SWIG Lib folder is a file called "attributes.i" which is not discussed in the documentation but that contains inline documentation.

All you have to do is add the following line to your interface file.

%include <attributes.i>

You then receive a number of macros (such as %attribute) for defining attributes from existing methods.

An excerpt from the documentation in the attributes.i file:

The following macros convert a pair of set/get methods into a "native" attribute. Use %attribute when you have a pair of get/set methods to a primitive type like in:

  %attribute(A, int, a, get_a, set_a);

  struct A
  {
    int get_a() const;
    void set_a(int aa);
  };
cdiggins
  • 17,602
  • 7
  • 105
  • 102
  • 5
    Should be `%include ` in the latest version of SWIG. – Homar Jan 02 '15 at 07:16
  • 1
    This is by far a clean method. Is this method applicable to aggregate types? If not any idea how and std::vector as a member in C++ class can be accessed as an attribute? – Imran Jun 29 '17 at 20:57
4

Ooh, this is tricky (and fun). SWIG doesn't recognize this as an opportunity to generate @property: I imagine it'd be all too easy to slip up and recognize lots of false positives if it weren't done really carefully. However, since SWIG won't do it in generating C++, it's still entirely possible to do this in Python using a small metaclass.

So, below, let's say we have a Math class that lets us set and get an integer variable named "pi". Then we can use this code:

example.h

#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

example.i

%module example

%{
    #define SWIG_FILE_WITH_INIT
    #include "example.h"
%}

[essentially example.h repeated again]

example.cpp

#include "example.h"

util.py

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__

somefile.py

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

So the end result is just that you have to create a wrapper class for every SWIG Python class and then type two lines: one to mark which methods should be converted in properties and one to bring in the metaclass.

hao
  • 10,138
  • 1
  • 35
  • 50
  • 1
    This looks like it should do the trick! It is a shame SWIG doesn't have a more direct mechanism for making properties. – fuzzy-waffle Jul 27 '09 at 09:00
  • Is there any declaration syntax that it will recognize as a property? getPropName/setPropName maybe? – Toji Sep 28 '09 at 15:35
  • It doesn't look like SWIG tries to generate those, although I only spent about an hour or so with SWIG documentation in writing the code for this answer and it's entirely possible there's a way I haven't come across. – hao Nov 01 '09 at 17:51
  • Hao Lian's answer is excellent but the specific PropertyVoodoo code seems to fail if there is more than one entry in the __properties__ list. I've inserted that code directly into %pythoncode blocks in my SWIG input file and seem to be close to getting past this very annoying problem. –  Jan 24 '10 at 00:39
4

The problem with Hao's ProperyVoodoo metaclass is that when there are multiple properties in the properties list, all the properties behave the same as the last one in the list. For example, if I had a list or property names ["x", "y", "z"], then the properties generated for all three would use the same accessors as "z".

After a little experimentation I believe I've determined that this problem is caused by the way Python handles closures (ie. names within nested functions that refer to variables in the containing scope). To solve the problem, you need to get local copies of the property name variable into the fget and fset methods. It's easy enough to sneak them in using default arguments:

# (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__

Note that it is, in fact, completely safe to give fget and fset the extra _prop parameters because the property() class will never explicitly pass values to them, which means they will always be the default value (that being a copy of the string referenced by prop at the time each fget and fset method was created).

Brad
  • 41
  • 2
1

From http://www.swig.org/Doc2.0/SWIGDocumentation.html#SWIG_adding_member_functions:

A little known feature of the %extend directive is that it can also be used to add synthesized attributes or to modify the behavior of existing data attributes. For example, suppose you wanted to make magnitude a read-only attribute of Vector instead of a method.

So in your example the following should work:

%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);
}
%}
cdiggins
  • 17,602
  • 7
  • 105
  • 102
1

I had the same problem and the advice to use %pythoncode worked for me. Here is what I did:

class Foo {
  // ...
  std::string get_name();
  bool set_name(const std::string & name);
};

In the wrapper:

%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))
%}
Ross Kinder
  • 128
  • 6