[Types-sig] Why have two hierarchies? (*EUREKA!*)
Just van Rossum
just@letterror.com
Fri, 4 Dec 1998 23:38:28 +0100
(For those of you with a short attention span, you can skip right through
to the section marked with ******)
At 10:22 PM +0100 12/3/98, Niels Ferguson wrote:
>I have been trying to follow the meta-class discussion (with varying
>success), so it is quite possible that my comments are due to my
>limited understanding of the subject.
I may have posted a few times on the subject, but trust me, most of the
time I feel just like that. (I gave up on trying not to embarrass Guido
with my stupidity a long time ago: I might as well not post anything ;-)
>I find the idea of having a meta-class hierarchy and a separate class
>derivation hierarchy confusing.
It is.
>My question is whether you cannot merge the
>two into a single framework.
After I first read your mail I wrote a little reply, which explained why
this is neccesary, and before I got to the end of the paragraph it occurred
to me that it is horse-shite. So: thankyou thankyou thankyou. I feel much
better now.
The complexity of all meta proposals I have seen so far (now, _maybe_ I
missed a few, and I definitely don't know Smalltalk, so there's your
disclaimer) -- including mine *and* Guido's -- are mindboggling. I mean, if
my head explodes every half hour about hing I make up myself, there's
something seriously wrong.
So I wrote up Yet Another Object Protocol Proposal. I tried to avoid the
M-word: It's true, the word itself turns your mind into an infinite
recursion. Still, I won't talk about Turtles either.
Btw, my new proposal nullifies many points I tried to make in my previous
postings, including my very own meta hook!
******
A proposal for a flat object hierarchy (a.k.a. "The Earth is Flat")
Executive summary
An instance of a class
is the _same_ thing as
a subclass of a class.
"Reserved" attributes
- Every object has a __bases__ attribute, which contains
a sequence of arbitrary length containing other objects.
Possibly classes.
- Every object has a __namespace__ attribute, which is a dict.
This is much the same as the current __dict__ but to avoid
confusion I picked a new name. It also makes implementation
possible with Today's Python.
- Every object has a __name__ attribute.
These three attributes are reserved. Access the these is handled by the
underlying implementation and cannot be trapped by users. In an actual
implementation in, say, C++ __namespace__ and __name__ could be optimized
out for very simple objects (numbers, strings), but __bases__ should always
be there.
Time for a random discision. Since I already got rid of the fundamental
difference between classes and instances, I needed a way to differentiate
them (!). This is because of the bound/unbound method issue. I chose to use
the __name__ attribute as a flag: if it is None, we're dealing with in
instance. If it's not None we're dealing with a class.
Attribute lookup
- If the object's __bases__ sequence is empty (ie. no bases),
fall back to the simple-most getattr scheme: see if
object.__namespace__ contains the attribute.
otherwise:
- Loop through object.__bases__ and ask for a "__getattr__"
attribute.
- If one is found, call it, it'll return the attribute or
raises AttributeError
- Check whether we are an instance or a class (ie. check at the
__name__ attribute), make methods bound or unbound accordingly.
Methods
Methods in my scheme are identical to instance methods as we know them in
Python today:
- The "im_func" attribute, which is the actual function object
- The "im_class" attribute, which is the class in which the method
was found.
- The "im_self" attribute, which is the instance to which the
method is bound (if any, so may be None).
I defined a new method for the method object, mainly for internal purposes:
- The "bind" method. It takes an instance as argument, and
replaces the "im_self" attribute with it.
That's not illegal: that's _cool_. The attribute lookup scheme uses itself
recursively to get inherited attibutes. Way at the end of the scheme we
test whether we are dealing with a class or an instance and either make
methods bound or unbound.
Since these methods will always be constructed on the fly there is no
danger in binding it more than once: it will only get _used_ after the
attribute lookup is completed.
(Implementation note: if we'd like to do method caching, which will quite
likely be an enormous performance boost, one could implement a method lock.
method.bind() will raise an exception when the method is "locked".)
Consequences
The main differences with the current Python object model are:
1) A "custom" __getattr__ will by definition _always_ be invoked,
and not only after "normal" lookup failed. This is because
this _is_ the normal lookup procedure.
2) No types, everything is handled through object.__bases__.
3) No __class__ attribute: everything is done through
object.__bases__.
The difference with other proposals seems to be:
1) There are not _really_ meta classes involved. In this scheme,
a meta class is conceptually identical to a base class. Since
any object can have multiple base classes, this conceptually
means objects can have multiple meta classes. What this is
useful for, I will leave up to others.
I agree with Gordon that the current meta class stuff seems only
useful for fancy getattr tricks. Since point (1) makes that possible
(as a consequence of concept of this object model, not as a cheap
trick!), we can _really_ do away with the concept of meta classes.
Poof! Nobody needs to understand. There is nothing to understand!
If someone with a Smalltalk background steps up and asks: how do
I write a meta class in Python, the answer will be: write a class,
then subclass it. "But that's not the same!" the may sputter. Well,
it is now.
It's so simple I can hardly believe anybody in this group will buy it.
Maybe it could actually be useful! Must destroy...
Appendix A
And now for the hottest part:
*** This Actually Works in Plain Vanilla Python 1.5! ***
On this page you'll find an implementation:
http://starship.skyport.net/~just/meta/Flat.py
Exhaustively commented. Sorry, no energy left for demos ("batteries not
included"). Look at the test code at the bottom for simple examples of how
compatible this is with current Python semantics.
For those of you who are lazy: it's very compatible.
Just