Extending Wrapped Objects in Python

Thanks to Python's flexibility, you can easily add new methods to a class, even after it was already created:

    >>> class C(object): pass
    >>>
    >>> ##a regular function
    >>> def C_str(self): return 'A C instance!'
    >>>
    >>> ##now we turn it in a member function
    >>> C.__str__ = C_str   
    >>>
    >>> c = C()
    >>> print c
    A C instance!
    >>> C_str(c)
    A C instance!

Yes, Python rox.

We can do the same with classes that were wrapped with Boost.Python. Suppose we have a class point in C++:

    class point {...};

    BOOST_PYTHON_MODULE(_geom)
    {
        class_<point>("point")...;
    }

If we are using the technique from the previous session, Creating Packages, we can code directly into geom/__init__.py:

    from _geom import *

    ##a regular function
    def point_str(self):
        return str((self.x, self.y))
        
    ##now we turn it into a member function
    point.__str__ = point_str

All point instances created from C++ will also have this member function! This technique has several advantages:

You can even add a little syntactic sugar with the use of metaclasses. Let's create a special metaclass that "injects" methods in other classes.

    ##The one Boost.Python uses for all wrapped classes. 
    ##You can use here any class exported by Boost instead of "point"
    BoostPythonMetaclass = point.__class__ 

    class injector(object):
        class __metaclass__(BoostPythonMetaclass):
            def __init__(self, name, bases, dict):
                for b in bases:
                    if type(b) not in (self, type):
                        for k,v in dict.items():
                            setattr(b,k,v)
                return type.__init__(self, name, bases, dict)

    ##inject some methods in the point foo
    class more_point(injector, point):
        def __repr__(self):
            return 'Point(x=%s, y=%s)' % (self.x, self.y)
        def foo(self):
            print 'foo!'

Now let's see how it got:

    >>> print point()
    Point(x=10, y=10)
    >>> point().foo()
    foo!

Another useful idea is to replace constructors with factory functions:

    _point = point

    def point(x=0, y=0):
        return _point(x, y)

In this simple case there is not much gained, but for constructurs with many overloads and/or arguments this is often a great simplification, again with virtually zero memory footprint and zero compile-time overhead for the keyword support.