Search code examples
pythonc++swigpython-extensions

How to add an alternative constructor to the target language (specifically Python) in SWIG wrapping C++ code


I am creating Python interfaces to some C++ code that I cannot change, using SWIG. One of the C++ classes has a constructor that creates a partially initialized object that cannot be used as yet, an initialization function has to be called on it first. I want to remedy this in Python by providing an alternative constructor that takes care of doing both things (acquisition and initialization) at the same time. Let's say in C++ I have

class X {
 public:
  X() {...}
  void init(T a) {...}
  ...
};

In C++ I have to instantiate X as

X x;
x.init(a);

In Python I would like to do

x = X(a)

My solution is a hack that depends on the target language and the specific way SWIG generates the wrapper code: in my .i file I have

%inline %{
X* new_X(T a) {
  X* ret = new X();
  ret->init(a);
  return ret;
}

%nodefaultctor X;
class X {
 public:
  ...
  %extend {
    %pythoncode {
      def __init__(self, *args):
          this = _modulename.new_X(*args)
          try:
            self.this.append(this)
          except:
            self.this = this
    }
  }
};

This works fine, but it is not very satisfactory:

  • it depends on how SWIG wraps constructors internally
  • it is totally target language dependent

This seems to be a relatively common use case, so does anyone know if there is a standard way?


Solution

  • The current answer by V-master does not work as is. But it can be made to work:

    %ignore X::X();
    
    // declaration of class X, e.g. %include X.h
    
    %extend X {
        X(T a) {
            X* newX = new X();
            newX->init(a);
            return newX;
        }
    };
    

    Admittedly, this looks a little dubious, but it works, and is essentially an example from the SWIG documentation here.

    It is important to note that:

    %extend works with both C and C++ code. It does not modify the underlying object in any way---the extensions only show up in the Python interface.

    So what this really does is to create a method (not even a class method, actually) that creates a new instance of X, calls init(a) on it and returns it. Because the syntax somewhat resembles a constructor, SWIG will wrap it as such.