Variable 'superclass'es?

Neel Krishnaswami neelk at brick.cswv.com
Thu May 6 22:52:08 EDT 1999


In article <37329e5c at news1.jps.net>, Zigron <zigron at jps.net> wrote:
>I think the word is 'superclass'..hm. Anyways.
>
>What I want is a class, that inherits from a variable class.
>See what i'm trying to do? Can it be done?

I see what you are doing to do. Two thoughts come to mind:

1. Python can do it, but the way you are doing it won't work.
2. However, it's probably a bad idea.
3. But it's fun to do anyway.

Let's deal with point one first. Your method won't work because of 
the way that Python resolves names. It does things by object reference,
when you write something like 

class Foo(Bar):
    [...]

The class Foo inherits from the *object* pointed to by the name Bar, 
at the time of initialization. (Remember that in Python, class and
function definitions are runtime statements.) This means that if you 
later write:

Bar = Quux

then the superclass of Foo won't change. In order to do what you 
want, you'll need to write a factory function to create the class
dynamically. Here's what you need:

class Parent1:
    pass

class Parent2:
    pass

def ChildFactory(parent):
    class Child(parent):
	pass
    return Child

Testing this in the interpreter, we 
see:

>>> Child1 = ChildFactory(Parent1)
>>> Child2 = ChildFactory(Parent2)

>>> print Child1.__bases__
(<class __main__.Parent1 at 80d5ce8>,)

>>> print Child2.__bases__
(<class __main__.Parent2 at 80d96f0>,)

The __bases__ special attribute of a class object displays the 
superclasses of that class. We can't assign to it, though, so
you can't change a class's superclasses that way. (Though you
can change an instance's class by assigning to its __class__
method.)

There are some funny side-effects to this approach that you
should remember when debugging. If you have two instances, 
each an instance of a different class produced by the factory
function:

>>> kid1 = Child1()
>>> kid2 = Child2()

If you print the class name using the __class__ attribute you
will get the name of the class definition in the ChildFactory
function:

>>> print kid1.__class__
__main__.Child
>>> print kid2.__class__
__main__.Child

This works ok when in actual code, though:

>>> print kid2.__class__ == kid1.__class__
0

It's just that the visual messages can be a bit confusing.

Now, we can move on to point 2, why this is probably a bad idea.

In general, whenever you want to dynamically manipulate your 
class hierarchy, this is a sign that inheritance is the wrong
tool for the job. It's hard to read, and sometimes you will get
bitten by non-unique names inside the classes, and worst of 
all you will confuse other programmers (including yourself after
six months) because most everyone associates classes with static
things that don't change during the program run. 

Still, very often you need the dynamism? What to do -- use delegation
instead. Instead of inheriting from a class to create a new class
at runtime, you write a class that receives another object as an
initialization variable. Then each method can dispatch on the object's
class at runtime. 

class Foo:
    def frob(self):
	print 'Foo!'

class Bar:
    def fiddle(self):
	print 'Bar!'

class Child:
    def __init__(self, obj):
	self.obj = obj
    def method(self):
	"""A method that dispatches based on self.obj's type."""
	if self.obj.__class__ == Foo:
	    self.obj.frob()
	elif self.obj.__class__ == Bar:
	    self.obj.fiddle()

kid1 = Child(Foo())
kid2 = Child(Bar())

Trying it out, we see:

>>> kid1.method()
'Foo!'
>>> kid2.method()
'Bar!'

Which is what we wanted. 

A third possibility (and you thought that only Perl had more than one
way to do it) is to create a metaclass that creates the child classes
as instances of itself. This isn't that hard either, but it would 
bloat this post past my endurance. So someone else can post it.

All of these approaches are basically equivalent, btw. The question
is what is appropriate for your problem. And that is up to you. (Also,
I'd appreciate it if real Python gurus could correct any of the mistakes
I have certainly made.)


Neel




More information about the Python-list mailing list