[Python-3000] PEP 3115 chaining rules (was Re: pep 3124 plans)

Talin talin at acm.org
Thu Aug 2 07:32:55 CEST 2007


Phillip J. Eby wrote:
> At 07:40 PM 7/31/2007 +1000, Nick Coghlan wrote:
>> Phillip J. Eby wrote:
>>> In other words, a class' metaclass has to be a derivative of all 
>>> its bases' metaclasses; ISTM that a __prepare__ namespace needs to 
>>> be a derivative in some sense of all its bases' __prepare__ 
>>> results.  This probably isn't enforceable, but the pattern should 
>>> be documented such that e.g. the overloading metaclass' __prepare__ 
>>> would return a mapping that delegates operations to the mapping 
>>> returned by its super()'s __prepare__, and the actual class 
>>> creation would be similarly chained.  PEP 3115 probably needs a 
>>> section to explain these issues and recommend best practices for 
>>> implementing __prepare__ and class creation on that basis.  I'll 
>>> write something up after I've thought this through some more.
>> A variant of the metaclass rule specific to __prepare__ might look 
>> something like:
>>   A class's metaclass providing the __prepare__ method must be a 
>> subclass of all of the class's base classes providing __prepare__ methods.
> 
> That doesn't really work; among other things, it would require 
> everything to be a dict subclass, since type.__prepare__() will 
> presumably return a dict.  Therefore, it really does need to be 
> delegation instead of inheritance, or it becomes very difficult to 
> provide any "interesting" properties.

I think you are on to something here.

I think that in order to 'mix' metaclasses, each metaclass needs to get 
a crack at the members as they are defined. The 'dict' object really 
isn't important - what's important is to be able to overload the 
creation of a class member.

I can think of a couple ways to accomplish this.

1) The first, and most brute force idea is to pass to a metaclass's 
__prepare__ statement an extra parameter containing the result of the 
previous metaclass's __prepare__ method. This would allow the 
__prepare__ statement to *wrap* the earlier metaclass's dict, 
intercepting the insertion operations or passing them through as needed.

In fact, you could even make it so that the first __prepare__ in the 
chain gets passed in a regular dict. So __prepare__ always gets a dict 
which it is supposed to wrap, although it can choose to ignore it.

2) The second idea is to recognize the fact that we were never all that 
interested in creating a special dict subclass; The reason we chose that 
is because it seemed like an easy way to hook in to the addition of new 
members, by overridding the dict's insertion function. In other words, 
what the metaclass wants is the ability to override *insertion*. So you 
could change the metaclass interface to make it so that insertion is 
overridable, but in an "event chain" way, so that each metaclass gets a 
shot at the insertion event as it occurs.

The problem here is that we need to support more than just insertion, 
but lookup (and possibly deletion) as well.

This also leads to the third idea, which I am sure that you - of all 
people - have already thought of, which is:

3) Use something like your generic function 'next method' pattern. In 
fact, go the whole way and say that"add_class_member(cls:Metaclass, 
name, member, next:next_method)" is a generic function, and then call 
next_method to inform the next metaclass in the chain.

There are two obvious problems here: First, we can't dispatch on 'cls' 
since it's not been created yet.

Second, the metaclass machinery is deep down inside the interpreter and 
operates at the very heart of Python. Which means that in order to use 
generic functions, they would have to be built-in to the heart of Python 
as well. While I would love to see that happen some day, I am not 
comfortable giving an untried, brand new module such 'blessed' status.

-- Talin


More information about the Python-3000 mailing list