repoman
repoman

Reputation: 3665

Swig and Python - different object instantation

I Have a question regarding swig wrapped objects generated on the Python side and wrapped objects generated on the C++ side. Suppose I have the following simple C++ class definitions

#include <vector>

class Sphere
{
public:
    Sphere(){};
};

class Container
{
public:
    Container() : data_(0) {};

    void add() {
        data_.push_back(Sphere());
    }

    Sphere & get(int i) { return data_[i]; }

    std::vector<Sphere> data_;
};

and the following swig setup

%module engine
%{
#define SWIG_FILE_WITH_INIT
#include "sphere.h"
%}

// -------------------------------------------------------------------------
// Header files that should be parsed by SWIG
// -------------------------------------------------------------------------
%feature("pythonprepend") Sphere::Sphere() %{
    print 'Hello'
%}
%include "sphere.h"

If I then do the following in Python

import engine
sphere_0 = engine.Sphere()
container = engine.Container()
container.add()
sphere_1 = container.get(0)

Then the first instantiation of the wrapped Sphere class does call the init method of the Python wrapping interface ('Hello' is printed).

However, the second, where the instance is generated on the C++ side does not ('Hello' is not printed).

Since my goal is to be able to add additional Python functionality to the object upon its construction, I'd be pleased to hear if anybody has any pointers for a correct approach to achieve this - for both of the above instantiation scenarios.

Best regards,

Mads

Upvotes: 1

Views: 645

Answers (1)

Dave
Dave

Reputation: 8109

I usually do things like this with explicit pythoncode blocks in the interface file:

%pythoncode %{
def _special_python_member_function(self):
     print "hello"
     self.rotate() # some function of Sphere
Sphere.new_functionality=_special_python_member_function
%}

So you can add arbitrary python functionality to the class, on top of what the SWIG interface provides. You may want/need to rename some of the C functionality out the way but this can should get you all of the member functions you want.

I've never tried to remap __init__ in this way, so I don't know how that would behave. Assuming that it won't work, you won't be able to ensure that the python objects have a given internal state (member variables) at construction.

What you will be forced to do is do lazy evaluation:

def function_that_depends_on_python_specific_state(self, *args):
   if not hasatttr( self, 'python_data'):
        self.python_data = self.make_python_data() # construct the relevant data
   pass # do work that involves the python specific data

and check for the existence of the python specific data. If there is just a few cases of this, I'd just put it in the functions as above. However, if that ends up being messy, you could modify __getattr__ so that it constructs the python-specific data members as they are accessed.

def _sphere_getattr(self, name):
    if name=='python_data':
         self.__dict__[name]=self.make_python_data()
         return self.__dict__[name]
    else:
        raise AttributeError
Sphere.__getattr__ = _sphere_getattr

IMHO, in the limit where you have a large amount of new functionality, and data that are independent of the underlying C implementation, you are in effect asking "How can I make my python Sphere class be a sub-class ofthe C Sphere class but keep them as the same type?"

Upvotes: 1

Related Questions