Welcome, guest | Sign In | My Account | Store | Cart

This is a recipe for defining class hierarchies outside of class definitions. This way you could at, runtime, decide what class should act as the parent of a given class. A class could sometimes have one parent, sometimes another.

Python, 63 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
from new import classobj

def connectClasses( newClassName, parentClass, childClass ):
	# Create an empty shell class with the passed in parentClass
	# as the parent.
	DupedChildClass =  classobj( newClassName, (parentClass,), {} )

	# Inject the parent class in to the child as a member, in case
	# the child wants to call the parent.
	DupedChildClass._parentClass = parentClass
	
	# Copy references to all the members of the child class passed in
	# to the new version of the child class connected to the 
	# given parent class.  i.e. fill in the dupedChildClass shell created
	# on previous line.	
	for k,v in childClass.__dict__.items():
		# U can't overwrite the doc string for some reason
		if k != '__doc__':
			setattr( DupedChildClass, k, v )
			
	return DupedChildClass

    
class Person:
      def sayHello( self ):
            print "Hi, I am a %s."  % self._getMyGender()
            
class Man( Person ):    
      def _getMyGender( self ):
            return "man"
        
class Woman( Person ): 
      def _getMyGender( self ):
            return "woman"    
 
class Insecure:
      def _getMyGender( self ):
            return self._parentClass._getMyGender( self ) + ', if that is OK' 

# Create the two new classes
InsecureMan = connectClasses( 'InsecureMan', Man, Insecure )
InsecureWoman = connectClasses( 'InsecureWoman', Woman, Insecure )

# Instantiate classes
iMan = InsecureMan()
iWoman = InsecureWoman()
man = Man()
woman = Woman()

# Test the classes
man.sayHello()
woman.sayHello()
iMan.sayHello()
iWoman.sayHello()


# Expected output
'''
Hi, I am a man.
Hi, I am a woman.
Hi, I am a man, if that is OK.
Hi, I am a woman, if that is OK.
'''

Motivation:

I had an existing class hierarchy, where I sometimes wanted to make the same modification to two different classes, while leaving the original classes untouched. I considered the decorator pattern because it let me add functionality to objects of different types by intercepting then proxying method calls. My client using the decorated object saw these objects behaving polymorphicly because the method interface was constant, while object behavior varies by object. However, the decorator did not give me true polymorphism because the decorator class is not in the inheritence tree of the object it decorates. Therefore if code inside the decorated object calls one of it’s own member functions it will never execute the matching method of the decorator. What if you wanted true inheritance, but also wanted to add the same behavior to multiple types of objects? Lets take an example…

Lets say we have a Person class. From that class we derive both a Man and a Woman class.

            PERSON                   

              |                        

------------------------------             

|                             |        

MAN                        WOMAN

What if I introduce the concept of an insecure person? Lets say that insecurity manifests itself the same way in both sexes. I want to be able create a regular man, a regular woman, an insecure man or an insecure woman.

Adding another stipulation, lets say that insecurity modifies the existing methods of a person in some way, as opposed to just adding new methods.

I don’t want multiple inheritance since I am modifying existing methods of the class, not adding new methods that the class does not have. I also don’t want a decorator, as we will see later, because there are non-public methods called internally that I want overridden as well. I don’t want to put IF statements in the code to check if we are insecure, since I think inheritance is more maintainable. I also don’t want to create insecure man and insecure woman classes separately because they would share exactly the same code.

What I really want…

            PERSON                   

              |                        

------------------------------             

|                             |        

MAN                        WOMAN   

|                             |

INSECURE INSECURE

In programming languages I’ve used, it is not possible to create the class hierarchy above at design time. The “insecure” class has two different parents, but only one parent at a time.

What if we could separate the definition of the class hierarchy from the class definition itself? e.g. class InsecureMan = Person -> Man -> Insecure

class InsecureWoman = Person -> Woman -> Insecure

In python, you can approximate such a language construct because you can create and manipulate classes on the fly. It is likely that someone better versed in metaclasses could do a cleaner job than I have done, but I include here a version that has been working for me.

Lets say I start with Insecure derived from Man. It is not safe to just take an instance of the Insecure class and change it’s parent class from Man to Woman if you desire an Insecure Woman instance. If you had existing instances of Insecure Men you will have transgendered them by changing the parent of the Insecure class, since they all share the same class definition. Therefore, before linking classes together to build a class heirarchy we create a class shell whose parent we can set, then copy over references to all the class members and methods.

Alternative Designs:

There are ways via composition to solve this particular problem. You could provide the person with a VoiceBox, or an InsecureVoiceBox derived from a VoiceBox. The InsecureVoiceBox adds in the “if that is OK.” when asked to speak. But, if there are a wide variety of methods that need modification, or if the voice box needed access to Person methods composition might not fit as well.

6 comments

Jonas Haag 15 years, 2 months ago  # | flag

Now you could do this in a (still not) clean(er) way, using .__class__:

In [1]: class MyClass:
   ...:     foo = 42
   ...:     bar = 23 

In [2]: class MySecondClass:
   ...:     def __init__(self, *args, **kwargs):
   ...:         # args aren't important here
   ...:         self.bizz = 1
   ...:         self.buzz = 2      

In [3]: class MyThirdClass:
   ...:     bizz = 1
   ...:     buzz = 2  

In [4]: class2 = MySecondClass()

In [5]: class2.bizz, class2.buzz
Out[5]: (1, 2)

In [6]: class2.__class__ = MyClass

In [7]: class2.bizz, class2.buzz, class2.foo, class2.bar
Out[7]: (1, 2, 42, 23)

In [8]: class3 = MyThirdClass()

In [9]: class3.bizz, class3.buzz
Out[9]: (1, 2)

In [10]: class3.__class__ = MyClass

In [11]: class3.foo, class3.bar
Out[11]: (42, 23)

In [12]: class3.bizz, class3.buzz
---------------------------------------------------------------------------
<type 'exceptions.AttributeError'>        Traceback (most recent call last)

/home/jonas/<ipython console> in <module>()

<type 'exceptions.AttributeError'>: MyClass instance has no attribute 'bizz'

The important thing is that you'll lose all class variables (but not instance variables!) changing the __class__ attribute.

Shalabh Chaturvedi 15 years, 2 months ago  # | flag

You could instead use multiple inheritance. It is possible to derive from a primary base class and a secondary mixin class that just provides some added functionality. In Python you can use super() so the mixin class can still call the other base class even though it doesn't know which one it is. You'd have to derive from object in Python versions < 3.0. Here is your example:

class Person(object):
    def sayHello( self ):
        print "Hi, I am a %s."  % self._getMyGender()

class Man(Person):    
    def _getMyGender(self):
        return "man"

class Woman(Person): 
    def _getMyGender(self):
        return "woman"    

class InsecureMixin(object):
    def _getMyGender(self):
        return super(InsecureMixin, self)._getMyGender() + ', if that is OK'

class InsecureMan(InsecureMixin, Man):
    pass

class InsecureWoman(InsecureMixin, Woman):
    pass

im = InsecureMan()
im.sayHello()       # prints: Hi, I am a man, if that is OK.
iw = InsecureWoman()
iw.sayHello()       # prints: Hi, I am a woman, if that is OK.

super() finds the next class in the MRO (method resolution order). The MRO is a list of all base classes of a class. The order of classes in the MRO depends on how you list the base classes in the class statement. For e.g. you can try this:

print InsecureMan.__mro__
print InsecureWoman.__mro__

Here's another mixin:

class LoudMixin(object):
    def _getMyGender(self):
        return super(LoudMixin, self)._getMyGender().upper()

Now you can combine either one or both mixins with either Man or Woman. If you use both mixins together, one mixin will end up calling the other.

Jonas Haag 15 years, 2 months ago  # | flag

Yes, but then you use the dynamics. Anyways, I don't think you even need that weird stuff using python :)

Jonas Haag 15 years, 2 months ago  # | flag

Dammit, s/use/lose/

Daniel Brodie 15 years, 2 months ago  # | flag

Well, you can also at runtime change the bases attribute, there are a few quicks with it but it can work if done right: class A(object): pass

class B(object):
    pass

class C(A):
    pass

C.__bases__ = (A, B)

tada!

Ted Skolnick (author) 15 years, 1 month ago  # | flag

I like the mixins. One thing to keep in mind with the bases trick above, is that I don't want to change the class heirarchy of any object already instantiated. If I want to create one object that is a C derived from a B, and another that is a C derived from an D, I don't want to mess up that first object's inheritance tree when I go about setting up the second object. If I modify the bases of C, I will be messing with any object already created from from class C.

Created by Ted Skolnick on Sun, 15 Feb 2009 (MIT)
Python recipes (4591)
Ted Skolnick's recipes (1)

Required Modules

Other Information and Tasks