5.14 Modifying the Class Hierarchy of an Instance
Credit: Ken Seehof
5.14.1 Problem
You need to modify
the class hierarchy of an instance object that has already been
instantiated.
5.14.2 Solution
A rather unusual application of the mix-in concept lets us perform
this task in Python 2.0 or later (with some limitations in Python
2.2):
def adopt_class(klass, obj, *args, **kwds):
're-class obj to inherit klass; call _ _init_ _ with *args, **kwds'
# In Python 2.2, klass and obj._ _class_ _ must be compatible,
# e.g., it's okay if they're both classic, as in the 'demo' function
classname = '%s_%s' % (klass._ _name_ _, obj._ _class_ _._ _name_ _)
obj._ _class_ _ = new.classobj(classname, (klass, obj._ _class_ _), {})
klass._ _init_ _(obj, *args, **kwds)
def demo( ):
class Sandwich:
def _ _init_ _(self, ingredients):
self.ingredients = ingredients
def _ _repr_ _(self):
return ' and '.join(self.ingredients)
class WithSpam:
def _ _init_ _(self, spam_count):
self.spam_count = spam_count
def _ _repr_ _(self):
return Sandwich._ _repr_ _(self) + self.spam_count * ' and spam'
pbs = Sandwich(['peanut butter', 'jelly'])
adopt_class(WithSpam, pbs, 2)
print pbs
5.14.3 Discussion
Sometimes class adoption, as illustrated by this
recipe, is the cleanest way out of class hierarchy problems that
arise when you wish to avoid module interdependencies (e.g., within a
layered architecture). It's more often useful if you
want to add functionality to objects created by third-party modules,
since modifying those modules' source code is
undesirable.
In the following example, the programmer has these constraints:
There are several classes in objects.py, and
more will be added in the future.
objects.py must not import or know about
graphics.py, since the latter is not available
in all configurations. Therefore, class G cannot
be a base class for the objects.py classes.
graphics.py should not require modification to
support additional classes that may be added to
objects.py.
#####################
# objects.py
class A(Base):
...
class B(Base):
...
def factory(...):
... returns an instance of A or B or ...
######################
# graphics.py
from oop_recipes import adopt_class
import objects
class G:
... provides graphical capabilities
def gfactory(...):
obj = objects.factory(...)
adopt_class(G, obj, ...)
return obj
Given the constraints, the
adopt_class
function provides a viable solution.
In Python 2.2, there are compatibility limitations on which classes
can be used to multiply inherit from (otherwise, you get a
"metatype conflict
among bases" TypeError
exception). These limitations affect multiple inheritance performed
dynamically by means of the
new.classobj
function (as in this recipe) in the same way as they affect multiple
inheritance expressed in the more usual way.
Classic classes (classes with no
built-in type among their ancestors, not even the new built-in type
object) can still be multiply inherited from quite
peaceably, so the example in this recipe keeps working. The example
given in the discussion will also keep working the same way, since
class G is classic. Only two new-style classes
with different built-in type ancestors would conflict.
5.14.4 See Also
The Library Reference section on built-in types,
especially the subsections on special attributes and functions.
|