Extending Wrapped Objects in Python

| 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:

- Cut down compile times to zero for these additional functions
- Reduce the memory footprint to virtually zero
- Minimize the need to recompile
- Rapid prototyping (you can move the code to C++ if required without changing the interface)

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.

Copyright © 2002-2003 David Abrahams
Copyright © 2002-2003 Joel de Guzman
Permission to copy, use, modify, sell and distribute this document is granted provided this copyright notice appears in all copies. This document is provided "as is" without express or implied warranty, and with no claim as to its suitability for any purpose.