OOF2: The Manual

Name

PythonExportable and PythonNative — C++ base classes for objects exported to Python

Synopsis

#include "common/pythonexportable.h

template <class TYPE> class PythonExportable;
template <class TYPE> class PythonNative; 

Overview

The PythonExportable base class is used to convert base class pointers exported to Python via SWIG into derived class Python objects.

Suppose you have a C++ class hierarchy with a base class MyExportable and derived classes Wine and Cheese, all of which are swigged. If you have a function MyExportable* f() that returns a pointer to an instance of Wine or Cheese, the SWIG generated Python code won't give you access to the derived parts of Wine and Cheese. The swigged f() will return a Python MyExportable. This is consistent with the way C++ does things — using a MyExportable* only gives you access to those parts of Wine and Cheese that are declared in the base class — but it's inconsistent with Python's approach. In Python there's no such thing as a base class pointer. All components of all objects are always accessible, if you know they're there. (For example, you might have created a SWIGged derived class object in Python, stored in a C++ list of base class objects, and then retrieved it from that list in Python. You'd expect to have all of the derived class functions available.)

The PythonExportable and PythonNative classes remedy this inconsistency, allowing C++ functions to have polymorphic Python return types.

Source Files

  • SRC/common/pythonexportable.h: C++ header file

PythonExportable

To use PythonExportable, you need to do two things:

  1. Derive a C++ class hierarchy from the templated base class PythonExportable. The template parameter must be the name of the class derived from PythonExportable. Each derived type must supply a classname() virtual function that returns the name of the derived class.[92]

    For example:

    class MyExportable : public PythonExportable<MyExportable> {
    };
    
    class Wine : public MyExportable {
    public:
       virtual const std::string& classname() const {
         static const std::string name("Wine");
          return name;
       }
    }
    
    class Cheese : public MyExportable {
    public:
       virtual const std::string& classname() const {
          static const std::string name("Cheese");
          return name;
       }
    } 

  2. In every swig file containing a function that returns a base class pointer (MyExportable* in these examples), include the line:

    PYTHONEXPORTABLE(MyExportable); 

    PYTHONEXPORTABLE is defined in SRC/common/typemaps.swg. It defines a swig typemap for the base class pointer that ensures that a derived class object is returned for any swigged function that returns a base class pointer. The function would appear in the swig file like:

    MyExportable* f1(); 

    PYTHONEXPORTABLE also defines a typemap for the base class name with New prepended. A function declared in the swig file like:

    NewMyExportable* f2(); 

    will also return a derived class object, but Python will take ownership of it, deleting it when there are no more references to it.

Now when f1() or f2() is called from Python, the returned value will be a Python instance of Wine or Cheese, and not a generic MyExportable.

PythonNative

If the C++ class hierarchy derived from PythonExportable is swigged, and the swigged Python classes are further extended by Python inheritance, then the above mechanism doesn't quite work. The class which is to be used as a base class for the Python inheritance must be derived virtually from PythonExportable. The derived classes must also be derived from PythonNative. PythonNative is a template, and must have the same template parameter as PythonExportable. The C++ constructor must have a PyObject* argument and pass it to the PythonNative constructor.

For example, if Cheese is swigged and extended in Python, then it needs to have been defined like this in C++:

class MyExportable : virtual public PythonExportable<MyExportable> {
};

class Cheese
   : public MyExportable, public PythonNative<MyExportable>
{
   public:
   Cheese(PyObject* self) : PythonNative(self) { ... }
   const std::string& classname();
} 

and a Python constructor for the derived class that also passes the pointer:

class Cheddar(Cheese):
    def __init__(self):
       [cheddar specific stuff]
       Cheese.__init__(self, self) 

The second self in the line above is the PyObject* self in the C++ Cheese constructor.[93]

classname() needs to be defined in the derived C++ class, but if only the Python classes derived from the C++ class are used, classname() will never be called.



[92] Intermediate abstract derived classes do not need to define classname().

[93] It is possible to hide the second self if desired. See SRC/engine/pypropertywrapper.spy for an example.