[Python-Dev] Classes and Metaclasses in Smalltalk

M.-A. Lemburg mal@lemburg.com
Thu, 03 May 2001 18:08:30 +0200


Guido van Rossum wrote:
> 
> > We are actually trying to turn classes into types here :-)
> 
> Yes!  Wait till you see my next batch of checkins. :-)

Looking forward to them :) 

BTW, can you give a good starting point into all this (code wise
and concept wise) ? I'd like to play around these new concepts
a litte to get a beeter feeling for the possible issues (I should
have done the same for the coercion stuff a year ago: implementing
mxNumber I now find that some important hooks are missing :-().
 
> > Really, I think that we could resolve this issue by not inheriting
> > from meta-classes. DictType is a creation of the meta-class
> > TypeType. I'm not calling these instances to prevent additional
> > confusion. The root of the problem is that for some reason there
> > is belief that DictType should implicitly inherit attributes and
> > methods from TypeType. If we simply say that there is no implicit
> > inheritance (only explicit one), then these problems should go
> > away.
> 
> Sorry, you still seem to be confused about this. 

I think it has to do with terminology: when I say "inherit"
I actually mean "the lookup is forwarded to the another object".

In that sense, instances inherit from their classes and 
classes from their base-classes:

meta-class M ->        o base-class A
                         o class B
                           o instance x = B()  

Meta-class M control this "inheritance scheme" and can modify
it depending on its needs. 

Here's a scenario of what I have in mind:

In the above picture, say A defines an attribute A.a which is not 
defined in B or as instance attribute of B(). Querying x.a would then 
launch this process:

1. x.a -> fails
2. M.__findattr__(x, 'a') is called to find and return the
   attribute
3. M.__findattr__ asks B for an attribute 'a' -> fails
4.    -- " --     asks A       -- " --        -> success
5.    -- " --     returns the found attribute

I know that this is somewhat different under the covers than
what's happening now, but the Python programmer will not notice
this. It most probably does not work well with the Don Beaudry
hook though... so maybe I'm simply on the wrong track here.

> As I tried to
> explain before, DictType does not *inherit* from TypeType, but it is
> an *instance* of TypeType.  TypeType defines a __repr__() method for
> all its instances.  This is needed so that repr(DictType) returns
> "<type 'DictType'>".  It is *not* inherited from TypeType!
> 
> If DictType were to inherit from something, it would inherit from the
> (not yet existing) ObjectType.  ObjectType would have a __repr__
> method too: it returns "<foo object at 0x......>".
> 
> But this method is overridden by DictType, so doesn't come into play.
> 
> Requiring explicit inheritance (whatever that may be) won't fix the
> problem.

With "explicit inheritance" I meant that the programmer has to
take care of passing the lookup on to the meta-class, rather
than applying some magic which hooks together class and meta-
class.
 
> > Some of these ideas are burried in the "super" part of this
> > thread. Unfortunately this concept doesn't go very far since
> > Python has multiple inheritance and thus the term "super"
> > (referring to the class' single base class) is not well-defined.
> 
> Not true.  While super can't always refer to a single class, the use
> of super can be completely well-defined in an unambiguous way.  Given
> 
>   class D(A, B, C):
>     def foo(self):
>       super.foo(self)
> 
> "super.foo" is whatever would be called in D1 if we changed the class
> hierarchy as follows:
> 
>   class D1(A, B, C): pass
>   class D(D1):
>     def foo(self):
>       D1.foo(self)

Nice trick -- much like the "+0" trick in math ;-)

> The problem with super is not that it isn't well-defined.  Its problem
> is that it's not enough to do what you want.  In some situations
> involving multiple inheritance, it can be essential to be able to
> "merge" methods of the sane name defined in each of the base classes,
> e.g.
> 
>   class C(A, B):
>     def save(self):
>       A.save(self)
>       B.save(self)
> 
> So we can't use super as an argument to abandon explicitly naming the
> base class of base methods.  Out of the proposed spellings that I can
> remember:
> 
>       B.save(self)                      # current Python
>       B.__dict__['save'](self)          # ditto, butt ugly
>       B::save(self)                     # C++
>       B._.save(self)                    # Don Beaudry
>       B.instanceMethods.save(self)      # ???
> 
> I still like current Python best!

But it doesn't help us in the very common case of mixin classes
since there the method and sometimes even not the programmer
will know where the basemethod to call lives. This is why I
wrote the basemethod() helper: it looks up the right method
at run-time and thus allows writing mixin-classes which override
methods of other classes which are only known to the programmer
using the mixin and not necessarily to the one writing the mixin.
 
> > As Jim mentioned in his reply to Thomas' question, SmallTalk
> > has two parallel hierarchies. One for the classes and one for
> > the meta-classes. If we follow the same path in Python and
> > keep the two well separated, I think we can resolve many of
> > the issues which are currently showing up.
> 
> Yeah, but this is not the path that Python has already taken (and
> which has been beaten further by Jim Fulton's ExtensionClasses).
> Python's path is "turtles all the way down".  See also my old
> head-exploding metaclasses paper.

I know... I was under the impression, though, that a little
breakage under the covers is allowed when moving from type/classes
to all types.
 
> > To link the two hierarchies together we don't need a "super"
> > concept, but instead a way to reach the meta-class in charge
> > of a class, say "klass.__creator__".
> 
> Your confusion between the "isInstanceOf" and "isInheritedFrom"
> relationships seems really deep!  Super relates to inheritance.
> Metaclasses relate to instantiation (of the class, as an instance of
> the metaclass).

See above... I don't like implicitely binding creation of objects
with lookup paths. These two concepts don't belong together, IMHO,
since they introduce restrictions which are not really necessary.
(I have made some great experience with loosly coupled object
systems and don't want to miss their flexibility anymore.)

> > Note that there's another issue hiding in all this and again
> > this is due to multiple inheritance: which meta-class is in
> > charge of a class which is derived from two classes having
> > different meta-classes ?
> >
> > meta1            -->         o klass1
> >                                o klass1a
> >                                o klass1b
> > meta2            -->         o klass2
> >                                o klass2a
> >                                o klass2b
> >
> > class klass3(klass1a, klass2b):
> >       ...
> >
> > I think there's no clean way to resolve this, so I'd suggest
> > to simply rule this out and declare it illegal (class can
> > only be based on classes having the same meta-class).
> 
> Unfortunately, again thanks to Jim Fulton, we can't rule this out,
> because this is actually used by ExtensionClasses.  The rule (as I
> interpret it) gives the first base class control; if the first base
> class is a standard class, it looks if any of the other base classes
> are not standard classes, and if so, gives control to the first such
> base class.  Another way to say this is that the first base class that
> has a non-standard metaclass gets control.

Ouch. Still, since Jim's in control of ExtensionClass -- wouldn't
it be possible to adapt ExtensionClass to an altered scheme ?

> (ExtensionClasses implements an additional rule where it requires all
> except one of the base classes to define no instance variables.  This
> is an example of the importance of metaclasses done right: the
> metaclass has control over such issues.  I don't think that
> Smalltalk's metaclasses have this much control -- you pretty much have
> a 1-1 correspondence between class and metaclass.

Right: more power to the meta-class :-)

-- 
Marc-Andre Lemburg
______________________________________________________________________
Company & Consulting:                           http://www.egenix.com/
Python Software:                        http://www.lemburg.com/python/