Newbie Class Questions

Joe Mason joe at notcharles.ca
Sun Mar 28 18:26:45 CEST 2004


In article <c46hup$d9d$05$1 at news.t-online.com>, Peter Otten wrote:
> Joe Mason wrote:
> 
>>>> I think it would be cool for my "AutoText" class (see below, hereby
>>>> GPL'd) to be able to tell what subclasses inherit from it and
>>>> instantiate objects for all of those subclasses to implement a "AutoAll"
>>>> function.. Any ideas?
>> 
>> Actually, somebody pointed out in the other thread that Python has a
>> __subclasses__ function, so AutoAll can just do "for i in
>> AutoText.__subclasses__:".
> 
> My first reaction was disbelief :-) Now we can change
> 
> # add as base whenever you create a new subclass of AutoText
> class AutoAll(AutoUrl, AutoEmphasis): 
>     pass
> 
> to
> 
> AutoAll = type("AutoAll", tuple(AutoText.__subclasses__()), {})

Woah.  That's a weird one.  You're on the right track, but what you're
doing here is creating a class with absolutely nothing in it - not even
the default attributes that I'll show below.  What you really want to do
is make class normally, but only override the base classes.

Fortunately, that's even easier:

    class AutoAll(tuple(AutoText.__subclasses__())):
        pass

... except that doesn't work, and nobody's been able to explain to me
why not.

So, I'll turn to metaclasses for the clean solution.  But first:

> Do you know what the last dictionary parameter of type() is for? A cursory
> glance over the 2.3 library did not bring up a usecase of type() with three
> parameters.

It's the __dict__ of the class that's about to be created:

>>> class Verbose(type):
...     def __new__(*args):
...             print "new: ", args
...             return type.__new__(*args)
...     def __init__(*args):
...             print "init: ", args
...             type.__init__(*args)
... 
>>> class Empty(object): __metaclass__ = Verbose
... 
new:  (<class '__main__.Verbose'>, 'Empty', (<type 'object'>,), {'__module__': '__main__', '__metaclass__': <class '__main__.Verbose'>})
init:  (<class '__main__.Empty'>, 'Empty', (<type 'object'>,), {'__module__': '__main__', '__metaclass__': <class '__main__.Verbose'>})
>>> class HasAnAttr(Empty): x = 1
... 
new:  (<class '__main__.Verbose'>, 'HasAnAttr', (<class '__main__.Empty'>,), {'x': 1, '__module__': '__main__'})
init:  (<class '__main__.HasAnAttr'>, 'HasAnAttr', (<class '__main__.Empty'>,), {'x': 1, '__module__': '__main__'})

(Hmm, I guess the __metaclass__ means that first one is not Empty after
all...)

The metaclass I just made hooks in to the class creation, in this case
just to print some extra stuff and then call the normal one.  Here's one
that replaces the bases cleanly:

    class AutoTextSubclasses(type):
        def __new__(metaclass, name, bases, attrs):
            return type.__new__(metaclass, name,
                tuple(AutoText.__subclasses__()), attrs)

    class AutoAll: __metaclass__ = AutoTextSubclasses

That'll throw away the normal "bases" list, and do the same thing your
type class did - make "bases" out of __subclasses__ - except it passes
through the attrs unchanged.

(You can replace both uses of "type" in AutoTextSubclasses with Verbose
to see what's happening.)

Joe



More information about the Python-list mailing list