Recipe 20.8. Adding a Method to a Class Instance at Runtime
Credit: Moshe Zadka
Problem
During debugging, you want to identify certain specific instance
objects so that print statements display more
information when applied to those specific objects.
Solution
The print statement implicitly calls the special
method _ _str_ _ of the class of each object
you're printing. Therefore, to ensure that
printing certain objects displays more
information, we need to give those objects new classes whose
_ _str_ _ special methods are suitably modified.
For example:
def add_method_to_objects_class(object, method, name=None):
if name is None:
name = method.func_name
class newclass(object._ _class_ _):
pass
setattr(newclass, name, method)
object._ _class_ _ = newclass
import inspect
def _rich_str(self):
pieces = [ ]
for name, value in inspect.getmembers(self):
# don't display specials
if name.startswith('_ _') and name.endswith('_ _'):
continue
# don't display the object's own methods
if inspect.ismethod(value) and value.im_self is self:
continue
pieces.extend((name.ljust(15), '\t', str(value), '\n'))
return ''.join(pieces)
def set_rich_str(obj, on=True):
def isrich( ):
return getattr(obj._ _class_ _._ _str_ _, 'im_func', None) is _rich_str
if on:
if not isrich( ):
add_method_to_objects_class(obj, _rich_str, '_ _str_ _')
assert isrich( )
else:
if not isrich( ):
return
bases = obj._ _class_ _._ _bases_ _
assert len(bases) == 1
obj._ _class_ _ = bases[0]
assert not isrich( )
Discussion
Here is a sample use of this recipe's
set_rich_str function, guarded in the usual way:
if _ _name_ _ == '_ _main_ _':# usual guard for example usageIn old versions of Python (and in Python 2.3 and 2.4, for backwards
class Foo(object):
def _ _init_ _(self, x=23, y=42):
self.x, self.y = x, y
f = Foo( )
print f
# emits: <_ _main_ _.Foo object at 0x38f770>
set_rich_str(f)
print f
# emits:
# x 23
# y 42
set_rich_str(f, on=False)
print f
# emits: <_ _main_ _.Foo object at 0x38f770>
compatibility on instances of classic classes), intrinsic lookup of
special methods (such as the intrinsic lookup for _ _str_
_ in a print statement) started on the
instance. In today's Python, in the new object model
that is recommended for all new code, the intrinsic lookup starts on
the instance's class, bypassing names set in the
instance's own _ _dict_ _. This
innovation has many advantages, but, at a first superficial look, it
may also seem to have one substantial disadvantage: namely, to make
it impossible to solve this recipe's Problem in the
general case (i.e., for instances that might belong to either classic
or new-style classes).Fortunately, that superficial impression is not correct, thanks to
Python's power of introspection and dynamism. This
recipe's function
add_method_to_objects_class shows how to change
special methods on a given object
obj's class, without
affecting other "sibling" objects
(i.e., other instances of the same class as
obj's): very simply,
start by changing the
obj's classthat
is, by setting obj._ _class_ _ to a newly made
class object (which inherits from the original class of
obj, so that anything we
don't explicitly modify remains unchanged). Once
you've done that, you can then alter the newly made
class object to your heart's contents.Function _rich_str shows how you can use
introspection to display a lot of information about a specific
instance. Specifically, we display every attribute of the instance
that doesn't have a special name (starting and
ending with two underscores), except the instances'
own bound methods. Function set_rich_str shows how
to set the _ _str_ _ special method of an
instance's class to either
"rich" (the
_rich_str function we just mentioned) or
"normal" (the _ _str_
_ method the object's original class is
coded to supply). To make the object's _
_str_ _ rich, set_rich_str uses
add_method_to_objects_class to set _ _str_
_ to _rich_str. When the object goes back
to "normal",
set_rich_str sets the object's
_ _class_ _ back to its original value (which is
preserved as the only base class when the object is set to use
_rich_str).
See Also
Recipe 20.6 and Recipe 20.7 for other cases in
which a class' methods are modified; documentation
on the inspect standard library module in the
Library Reference.