[Python-checkins] CVS: python/nondist/peps pep-0253.txt,1.11,1.12

Guido van Rossum gvanrossum@users.sourceforge.net
Wed, 11 Jul 2001 14:26:10 -0700


Update of /cvsroot/python/python/nondist/peps
In directory usw-pr-cvs1:/tmp/cvs-serv3385

Modified Files:
	pep-0253.txt 
Log Message:
Exhausted myself today by adding a long section on the new MRO.


Index: pep-0253.txt
===================================================================
RCS file: /cvsroot/python/python/nondist/peps/pep-0253.txt,v
retrieving revision 1.11
retrieving revision 1.12
diff -C2 -r1.11 -r1.12
*** pep-0253.txt	2001/07/11 19:09:28	1.11
--- pep-0253.txt	2001/07/11 21:26:08	1.12
***************
*** 142,149 ****
      metatypes are typically written in C, and may be shared between
      many regular types. (It will be possible to subtype metatypes in
!     Python, so it won't be absolutely necessary to write C in order to
!     use metatypes; but the power of Python metatypes will be limited.
!     For example, Python code will never be allowed to allocate raw
!     memory and initialize it at will.)
  
      Metatypes determine various *policies* for types,such as what
--- 142,149 ----
      metatypes are typically written in C, and may be shared between
      many regular types. (It will be possible to subtype metatypes in
!     Python, so it won't be absolutely necessary to write C to use
!     metatypes; but the power of Python metatypes will be limited.  For
!     example, Python code will never be allowed to allocate raw memory
!     and initialize it at will.)
  
      Metatypes determine various *policies* for types,such as what
***************
*** 383,390 ****
      for the tp_clear() slot.  This turned out to be a bad idea.)
  
!     In order to be usefully subtyped in C, a type must export the
!     structure declaration for its instances through a header file, as
!     it is needed in order to derive a subtype.  The type object for
!     the base type must also be exported.
  
      If the base type has a type-checking macro (like PyDict_Check()),
--- 383,390 ----
      for the tp_clear() slot.  This turned out to be a bad idea.)
  
!     To be usefully subtyped in C, a type must export the structure
!     declaration for its instances through a header file, as it is
!     needed to derive a subtype.  The type object for the base type
!     must also be exported.
  
      If the base type has a type-checking macro (like PyDict_Check()),
***************
*** 482,490 ****
      tp_new() and tp_dealloc() slots, respectively.
  
!     In order to complete the initialization of the type,
!     PyType_InitDict() must be called.  This replaces slots initialized
!     to zero in the subtype with the value of the corresponding base
!     type slots.  (It also fills in tp_dict, the type's dictionary, and
!     does various other initializations necessary for type objects.)
  
      A subtype is not usable until PyType_InitDict() is called for it;
--- 482,490 ----
      tp_new() and tp_dealloc() slots, respectively.
  
!     To complete the initialization of the type, PyType_InitDict() must
!     be called.  This replaces slots initialized to zero in the subtype
!     with the value of the corresponding base type slots.  (It also
!     fills in tp_dict, the type's dictionary, and does various other
!     initializations necessary for type objects.)
  
      A subtype is not usable until PyType_InitDict() is called for it;
***************
*** 494,499 ****
      to initialize the subtype in their constructor function.  It is
      allowed to call PyType_InitDict() more than once; the second and
!     further calls have no effect.  In order to avoid unnecessary
!     calls, a test for tp_dict==NULL can be made.
  
      (During initialization of the Python interpreter, some types are
--- 494,499 ----
      to initialize the subtype in their constructor function.  It is
      allowed to call PyType_InitDict() more than once; the second and
!     further calls have no effect.  To avoid unnecessary calls, a test
!     for tp_dict==NULL can be made.
  
      (During initialization of the Python interpreter, some types are
***************
*** 638,642 ****
  
  
! Multiple Inheritance
  
      The Python class statement supports multiple inheritance, and we
--- 638,642 ----
  
  
! Multiple inheritance
  
      The Python class statement supports multiple inheritance, and we
***************
*** 726,733 ****
--- 726,882 ----
  
  
+ Method resolution order (the lookup rule)
+ 
+     With multiple inheritance comes the question of method resolution
+     order: the order in which a class or type and its bases are
+     searched looking for a method of a given name.
+ 
+     In classic Python, the rule is given by the following recursive
+     function, also known as the left-to-right depth-first rule:
+ 
+       def classic_lookup(cls, name):
+           if cls.__dict__.has_key(name):
+               return cls.__dict__[name]
+           for base in cls.__bases__:
+               try:
+                   return classic_lookup(base, name)
+               except AttributeError:
+                   pass
+           raise AttributeError, name
+ 
+     The problem with this becomes apparent when we consider a "diamond
+     diagram":
+ 
+                 class A:
+                   ^ ^  def save(self): ...
+                  /   \
+                 /     \
+                /       \
+               /         \
+           class B     class C:
+               ^         ^  def save(self): ...
+                \       /
+                 \     /
+                  \   /
+                   \ /
+                 class D
+ 
+     Arrows point from a subtype to its base type(s).  This particular
+     diagram means B and C derive from A, and D derives from B and C
+     (and hence also, indirectly, from A).
+ 
+     Assume that C overrides the method save(), which is defined in the
+     base A.  (C.save() probably calls A.save() and then saves some of
+     its own state.)  B and D don't override save().  When we invoke
+     save() on a D instance, which method is called?  According to the
+     classic lookup rule, A.save() is called, ignoring C.save()!
+ 
+     This is not good.  It probably breaks C (its state doesn't get
+     saved), defeating the whole purpose of inheriting from C in the
+     first place.
+ 
+     Why was this not a problem in classic Python?  Diamond diagrams is
+     found rarely in classic Python class hierarchies.  Most class
+     hierarchies use single inheritance, and multiple inheritance is
+     usually confined to mix-in classes.  In fact, the problem shown
+     here is probably the reason why multiple inheritance is impopular
+     in classic Python.
+ 
+     Why will this be a problem in the new system?  The 'object' type
+     at the top of the type hierarchy defines a number of methods that
+     can usefully be extended by subtypes, for example __getattr__().
+ 
+     (Aside: in classic Python, the __getattr__() method is not really
+     the implementation for the get-attribute operation; it is a hook
+     that only gets invoked when an attribute cannot be found by normal
+     means.  This has often been cited as a shortcoming -- some class
+     designs have a legitimate need for a __getattr__() method that
+     gets called for *all* attribute references.  But then of course
+     this method has to be able to invoke the default implementation
+     directly.  The most natural way is to make the default
+     implementation available as object.__getattr__(self, name).)
+ 
+     Thus, a classic class hierarchy like this:
+ 
+           class B     class C:
+               ^         ^  def __getattr__(self, name): ...
+                \       /
+                 \     /
+                  \   /
+                   \ /
+                 class D
+ 
+     will change into a diamond diagram under the new system:
+ 
+                 object:
+                   ^ ^  __getattr__()
+                  /   \
+                 /     \
+                /       \
+               /         \
+           class B     class C:
+               ^         ^  def __getattr__(self, name): ...
+                \       /
+                 \     /
+                  \   /
+                   \ /
+                 class D
+ 
+     and while in the original diagram C.__getattr__() is invoked,
+     under the new system with the classic lookup rule,
+     object.__getattr__() would be invoked!
+ 
+     Fortunately, there's a lookup rule that's better.  It's a bit
+     difficult to explain, but it does the right thing in the diamond
+     diagram, and it is the same as the classic lookup rule when there
+     are no diamonds in the inheritance graph (when it is a tree).
+ 
+     The new lookup rule constructs a list of all classes in the
+     inheritance diagram in the order in which they will be searched.
+     This construction is done at class definition time to save time.
+     To explain the new lookup rule, let's first consider what such a
+     list would look like for the classic lookup rule.  Note that in
+     the presence of diamonds the classic lookup visits some classes
+     multiple times.  For example, in the ABCD diamond diagram above,
+     the classic lookup rule visits the classes in this order:
+ 
+       D, B, A, C, A
+ 
+     Note how A occurs twice in the list.  The second occurrence is
+     redundant, since anything that could be found there would already
+     have been found when searching the first occurrence.
+ 
+     We use this observation to explain our new lookup rule.  Using the
+     classic lookup rule, construct the list of classes that would be
+     searched, including duplicates.  Now for each class that occurs in
+     the list multiple times, remove all occurrences except for the
+     last.  The resulting list contains each ancestor class exactly
+     once (including the most derived class, D in the example).
+ 
+     Searching for methods in this order will do the right thing for
+     the diamond diagram.  Because of the way the list is constructed,
+     it does not change the search order in situations where no diamond
+     is involved.
+ 
+     Isn't this backwards incompatible?  Won't it break existing code?
+     It would, if we changed the method resolution order for all
+     classes.  However, in Python 2.2, the new lookup rule will only be
+     applied to types derived from built-in types, which is a new
+     feature.  Class statements without a base class create "classic
+     classes", and so do class statements whose base classes are
+     themselves classic classes.  For classic classes the classic
+     lookup rule will be used. (To experiment with the new lookup rule
+     for classic classes, you will be able to specify a different
+     metaclass explicitly.)  We'll also provide a tool that analyzes a
+     class hierarchy looking for methods that would be affected by a
+     change in method resolution order.
+ 
+ 
  XXX To be done
  
      Additional topics to be discussed in this PEP:
  
+       - backwards compatibility issues!!!
+ 
        - class methods and static methods
  
***************
*** 737,742 ****
        - built-in names for built-in types (object, int, str, list etc.)
  
-       - method resolution order
- 
        - __dict__
  
--- 886,889 ----
***************
*** 748,751 ****
--- 895,902 ----
  
        - API docs for all the new functions
+ 
+       - using __new__
+ 
+       - writing metaclasses (using mro() etc.)
  
        - high level user overview