[Python-checkins] peps: Use C3-based linearization for ABC support to improve predictability

lukasz.langa python-checkins at python.org
Mon Jul 1 14:46:25 CEST 2013


http://hg.python.org/peps/rev/000a8986ef73
changeset:   4968:000a8986ef73
user:        Łukasz Langa <lukasz at langa.pl>
date:        Mon Jul 01 14:46:15 2013 +0200
summary:
  Use C3-based linearization for ABC support to improve predictability

files:
  pep-0443.txt |  73 ++++++++++++++++++++++-----------------
  1 files changed, 41 insertions(+), 32 deletions(-)


diff --git a/pep-0443.txt b/pep-0443.txt
--- a/pep-0443.txt
+++ b/pep-0443.txt
@@ -193,48 +193,37 @@
 importantly, it introduces support for Abstract Base Classes (ABC).
 
 When a generic function implementation is registered for an ABC, the
-dispatch algorithm switches to a mode of MRO calculation for the
-provided argument which includes the relevant ABCs. The algorithm is as
-follows::
+dispatch algorithm switches to an extended form of C3 linearization,
+which includes the relevant ABCs in the MRO of the provided argument.
+The algorithm inserts ABCs where their functionality is introduced, i.e.
+``issubclass(cls, abc)`` returns ``True`` for the class itself but
+returns ``False`` for all its direct base classes. Implicit ABCs for
+a given class (either registered or inferred from the presence of
+a special method like ``__len__()``) are inserted directly after the
+last ABC explicitly listed in the MRO of said class.
 
-  def _compose_mro(cls, haystack):
-      """Calculates the MRO for a given class `cls`, including relevant
-      abstract base classes from `haystack`."""
-      bases = set(cls.__mro__)
-      mro = list(cls.__mro__)
-      for regcls in haystack:
-          if regcls in bases or not issubclass(cls, regcls):
-              continue   # either present in the __mro__ or unrelated
-          for index, base in enumerate(mro):
-              if not issubclass(base, regcls):
-                  break
-          if base in bases and not issubclass(regcls, base):
-              # Conflict resolution: put classes present in __mro__
-              # and their subclasses first.
-              index += 1
-          mro.insert(index, regcls)
-      return mro
-
-In its most basic form, it returns the MRO for the given type::
+In its most basic form, this linearization returns the MRO for the given
+type::
 
   >>> _compose_mro(dict, [])
   [<class 'dict'>, <class 'object'>]
 
-When the haystack consists of ABCs that the specified type is a subclass
-of, they are inserted in a predictable order::
+When the second argument contains ABCs that the specified type is
+a subclass of, they are inserted in a predictable order::
 
   >>> _compose_mro(dict, [Sized, MutableMapping, str,
   ...                     Sequence, Iterable])
   [<class 'dict'>, <class 'collections.abc.MutableMapping'>,
-   <class 'collections.abc.Iterable'>, <class 'collections.abc.Sized'>,
+   <class 'collections.abc.Mapping'>, <class 'collections.abc.Sized'>,
+   <class 'collections.abc.Iterable'>, <class 'collections.abc.Container'>,
    <class 'object'>]
 
 While this mode of operation is significantly slower, all dispatch
 decisions are cached. The cache is invalidated on registering new
 implementations on the generic function or when user code calls
-``register()`` on an ABC to register a new virtual subclass. In the
-latter case, it is possible to create a situation with ambiguous
-dispatch, for instance::
+``register()`` on an ABC to implicitly subclass it. In the latter case,
+it is possible to create a situation with ambiguous dispatch, for
+instance::
 
   >>> from collections import Iterable, Container
   >>> class P:
@@ -261,20 +250,38 @@
   RuntimeError: Ambiguous dispatch: <class 'collections.abc.Container'>
   or <class 'collections.abc.Iterable'>
 
-Note that this exception would not be raised if ``Iterable`` and
-``Container`` had been provided as base classes during class definition.
-In this case dispatch happens in the MRO order::
+Note that this exception would not be raised if one or more ABCs had
+been provided explicitly as base classes during class definition. In
+this case dispatch happens in the MRO order::
 
   >>> class Ten(Iterable, Container):
   ...     def __iter__(self):
   ...         for i in range(10):
   ...             yield i
   ...     def __contains__(self, value):
-  ...       return value in range(10)
+  ...         return value in range(10)
   ...
   >>> g(Ten())
   'iterable'
 
+A similar conflict arises when subclassing an ABC is inferred from the
+presence of a special method like ``__len__()`` or ``__contains__()``::
+
+  >>> class Q:
+  ...   def __contains__(self, value):
+  ...     return False
+  ...
+  >>> issubclass(Q, Container)
+  True
+  >>> Iterable.register(Q)
+  >>> g(Q())
+  Traceback (most recent call last):
+  ...
+  RuntimeError: Ambiguous dispatch: <class 'collections.abc.Container'>
+  or <class 'collections.abc.Iterable'>
+
+An early version of the PEP contained a custom approach that was simpler
+but created a number of edge cases with surprising results [#why-c3]_.
 
 Usage Patterns
 ==============
@@ -378,6 +385,8 @@
    a particular annotation style".
    (http://www.python.org/dev/peps/pep-0008)
 
+.. [#why-c3] http://bugs.python.org/issue18244
+
 .. [#pep-3124] http://www.python.org/dev/peps/pep-3124/
 
 .. [#peak-rules] http://peak.telecommunity.com/DevCenter/PEAK_2dRules

-- 
Repository URL: http://hg.python.org/peps


More information about the Python-checkins mailing list