I l@ve RuBoard Previous Section Next Section

15.11 Adding a Method to a Class Instance at Runtime

Credit: an anonymous contributor, Moshe Zadka

15.11.1 Problem

During debugging, you want to identify certain specific instance objects so that print statements display more information when applied to those objects.

15.11.2 Solution

The print statement implicitly calls an object's _ _str_ _ special method, so we can rebind the _ _str_ _ attribute of the object to a suitable new bound method, which the new module lets us build:

import string
import new

def rich_str(self):
    classStr = ''
    for name, value in self._ _class_ _._ _dict_ _.items(
            ) + self._ _dict_ _.items( ):
        classStr += string.ljust(name, 15) + '\t' + str(value) + '\n'
    return classStr

def addStr(anInstance):
    anInstance._ _str_ _ = new.instancemethod(rich_str,
        anInstance, anInstance._ _class_ _)

# Test it
class TestClass:
    classSig = 'My Sig'
    def _ _init_ _(self, a = 1, b = 2, c = 3):
        self.a = a
        self.b = b
        self.c = c

test = TestClass(  )
addStr(test)
print test

15.11.3 Discussion

This recipe demonstrates the runtime addition of a _ _str_ _ special method to a class instance. Python calls obj._ _str_ _ when you ask for str(obj) or when you print obj. Changing the _ _str_ _ special method of obj lets you display more information for the specific instance object in question when the instance is printed during debugging.

The recipe as shown is very simple and demonstrates the use of the special attributes _ _dict_ _ and _ _class_ _. A serious defect of this approach is that it creates a reference cycle in the object. Reference cycles are no longer killers in Python 2.0 and later, particularly because we're focusing on debugging-oriented rather than production code. Still, avoiding reference cycles, when feasible, makes your code faster and more responsive, because it avoids overloading the garbage-collection task with useless work. The following function will add any function to an instance in a cycle-free way by creating a specially modified class object and changing the instance's class to it:

def add_method(object, method, name=None):
    if name is None: name = method.func_name
    class newclass(object._ _class_ _):
        pass
    setattr(newclass, name, method)
    object._ _class_ _ = newclass

We could also use the new module to generate the new class object, but there is no particular reason to do so, as the class statement nested inside the add_method function suits our purposes just as well.

With this auxiliary function, the addStr function of the recipe can, for example, be more effectively (and productively) coded as:

def addStr(anInstance):
    add_method(anInstance, rich_str, '_ _str_ _')

The second approach also works for new-style classes in Python 2.2. The _ _class_ _ attribute of such an instance object is assignable only within certain constraints, but because newclass extends the object's existing class, those constraints are met (unless some strange metaclass is in use). In Python 2.2, operations on instances of new-style classes don't use special methods bound in the instance, but only special methods bound in the class (in all other cases, per-instance binding still override per-class bindings).

15.11.4 See Also

Recipe 15.7; Recipe 5.14 and Recipe 15.10 for other approaches to modifying the methods of an instance; documentation on the new standard library module in the Library Reference.

    I l@ve RuBoard Previous Section Next Section