[Zope-dev] New-style ExtensionClasses (Zope 2.8) -- MRO issue
Jim Fulton
jim at zope.com
Fri Oct 31 12:14:27 EST 2003
I've been working on a "new-style" ExtensionClass (nsEC) project to
reimplement ExtensionClass as a new-style meta class. This will allow
ExtensionClasses (i.e. most Zope 2 classes) to be able to use features
of new-style Python classes, including:
- New protocols,
- Descriptors,
- Garbage Collection,
...
I think I've got this largely working, but have encountered a
significant issue that I'd like some input on.
New-style classes and classic classes use different algorithms for
looking up inherited attributes (e.g. methods). Classic classes use a
left-to-right depth-first algorithm. Python 2.3 uses a "C3
Method-Resolution Order" algorithm. I won't try to explain how this
algorithm works. If you are curious, see:
http://www.python.org/2.3/mro.html
This new algorithm has a couple of noteworthy properties:
- It yields different lookup results than the left-to-right
depth-first algorithm used for classic classes.
- It is sometimes impossible to compute a "method resolution order"
for a given class and base classes.
>>> class A(object):
... pass
>>> class B(A):
... pass
>>> class C(A, B):
... pass
Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: Cannot create a consistent method resolution
order (MRO) for bases B, A
The original ExtensionClass used the left-to-right depth-first
algorithm used for classic classes. Currently, nsEC inherits the new
method lookup algorithm from type (the standard Python 2.3 metaclass
used for new-style classes). This has run into significant
difficulties with the Zope 2 classes, which have a highly complex
inheritence graph.
An initial analysis showed that for more than half of the classes in
Zope 2, it was impossible to compute a method-resolution order using
the new algorithm. After about a day of analysis (and development of
a tool to aid in the analysis) I've been able to adjust the Zope
classes so that they will work with the new algorithm.
I also analyzed the CMF head. To use the new algorithm with the CMF,
it was necessary to make some changes to the CMF and to Zope. In one
case, it was necessary to copy some methods around.
An interesting result of the analysis is that it identified several
methods that were being inherited incorrectly. The new inheritence
algorithm yeilded better results for these cases, once the classes
were modified enough to use the algorithm at all.
It is very likely that, if we use the new algorithm for nsEC, many
products will not work anymore (meaning with Zope 2.8). Modifying the
products to work will probably be a non-trivial undertaking.
We have three alternatives:
1. Use the old method-lookup algorithm for nsEC.
Pro: backward compatible
Con: may produce odd results if a new-style class with a complex
arrangement of base classes is mixed with an ExtensionClass.
2. Use the new-style method-lookup algithm
Pro: Most consistent with new-style classes.
Cons: Likely that many products will be broken and require change
to work with nsEC/Zope 2.8.
Analysis of the changes needed to fix method-resolution-order
problems is extremely complex.
3. Use a hybrid schema. I'll call this the "encapsulated base"
scheme.
Consider:
>>> class C(X, Y, Z):
... def foo(self): ...
... ...
When we look up an attribute, we do the following:
- Look in C's dictionary first. All three algorithms agree on
this. :)
- Look up the attribute in X. We don't care how we het the
attribute from X. If X is a new-style-class, we use the new
algithm. If X is a classic class, we use left-to-right
depth-first. If X is an nsEC, use the "encapsulated base"
algithm.
If we don't find the attribute in X, look in Y and then in Z,
using the same approach.
This algithm will produce backward compatible results, providing
the equivalent of left-to-right depth-first for nsECs and classic
classes.
(Note for the more advanced reader:
we'll actually do something less abstract. We'll use a simple
algorthm to merge the __mro__ of the base classes, computing an
__mro__ for classes classes using the left-to-right depth-first
algorithm. We'll basically lay the mros end-to-end left-to-right
and remove repeats, keeping the first occurence of each class.)
I find this approach appealing for reasons of encapsulation. When
reasoning about C, I can reason from known behavior of X, Y, and Z,
without considering their respective inherietnce hierarchies, which
I consider to be an implementation detail.
Pros: backward compatible
Less likely than option 1 to produce odd results if a
new-style class with a complex arrangement of base classes is
mixed with an ExtensionClass.
Con: may produce odd results if a new-style class with a complex
arrangement of base classes is mixed with an ExtensionClass.
Thoughts?
I am worried enough about breaking products that I'm inclined to go
with option 3.
Does anybody think we ought to use the new algorithm (option 2)?
Jim
--
Jim Fulton mailto:jim at zope.com Python Powered!
CTO (540) 361-1714 http://www.python.org
Zope Corporation http://www.zope.com http://www.zope.org
More information about the Zope-Dev
mailing list