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
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:
#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 = [] 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
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.