[Python-checkins] peps: Resolve the open issue on ABC support, link the reference implementation.

lukasz.langa python-checkins at python.org
Sat May 25 14:03:42 CEST 2013


http://hg.python.org/peps/rev/66ff54bad2ba
changeset:   4908:66ff54bad2ba
user:        Łukasz Langa <lukasz at langa.pl>
date:        Sat May 25 14:03:31 2013 +0200
summary:
  Resolve the open issue on ABC support, link the reference implementation.

files:
  pep-0443.txt |  90 ++++++++++++++++++++++++++++++++++++----
  1 files changed, 81 insertions(+), 9 deletions(-)


diff --git a/pep-0443.txt b/pep-0443.txt
--- a/pep-0443.txt
+++ b/pep-0443.txt
@@ -8,7 +8,7 @@
 Type: Standards Track
 Content-Type: text/x-rst
 Created: 22-May-2013
-Post-History: 22-May-2013
+Post-History: 22-May-2013, 25-May-2013
 Replaces: 245, 246, 3124
 
 
@@ -147,13 +147,8 @@
 
 The functionality described in this PEP is already implemented in the
 ``pkgutil`` standard library module as ``simplegeneric``. Because this
-implementation is mature, the goal is to move it largely as-is.
-
-The current implementation relies on ``__mro__`` alone, it will be made
-compatible with Abstract Base Classes' ``register()``/``unregister()``
-functionality. A possible solution has been proposed by PJE on the
-original issue for exposing ``pkgutil.simplegeneric`` as part of the
-``functools`` API [#issue-5135]_.
+implementation is mature, the goal is to move it largely as-is. The
+reference implementation is available on hg.python.org [#ref-impl]_.
 
 The dispatch type is specified as a decorator argument. An alternative
 form using function annotations has been considered but its inclusion
@@ -165,6 +160,80 @@
 following the convention on registering virtual subclasses on Abstract
 Base Classes, the dispatch registry will not be thread-safe.
 
+Abstract Base Classes
+---------------------
+
+The ``pkgutil.simplegeneric`` implementation relied on several forms of
+method resultion order (MRO). ``@singledispatch`` removes special
+handling of old-style classes and Zope's ExtensionClasses. More
+importantly, it introduces support for Abstract Base Classes (ABC).
+
+When a generic function overload 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::
+
+  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
+
+While this mode of operation is significantly slower, no caching is
+involved because user code may ``register()`` a new class on an ABC at
+any time. In such case, it is possible to create a situation with
+ambiguous dispatch, for instance::
+
+  >>> from collections import Iterable, Container
+  >>> class P:
+  ...     pass
+  >>> Iterable.register(P)
+  <class '__main__.P'>
+  >>> Container.register(P)
+  <class '__main__.P'>
+
+Faced with ambiguity, ``@singledispatch`` refuses the temptation to
+guess::
+
+  >>> @singledispatch
+  ... def g(arg):
+  ...     return "base"
+  ...
+  >>> g.register(Iterable, lambda arg: "iterable")
+  <function <lambda> at 0x108b49110>
+  >>> g.register(Container, lambda arg: "container")
+  <function <lambda> at 0x108b491c8>
+  >>> g(P())
+  Traceback (most recent call last):
+  ...
+  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::
+
+  >>> class Ten(Iterable, Container):
+  ...     def __iter__(self):
+  ...         for i in range(10):
+  ...             yield i
+  ...     def __contains__(self, value):
+  ...       return value in range(10)
+  ...
+  >>> g(Ten())
+  'iterable'
+
 
 Usage Patterns
 ==============
@@ -256,7 +325,8 @@
 References
 ==========
 
-.. [#issue-5135] http://bugs.python.org/issue5135
+.. [#ref-impl]
+   http://hg.python.org/features/pep-443/file/tip/Lib/functools.py#l359
 
 .. [#pep-0008] PEP 8 states in the "Programming Recommendations"
    section that "the Python standard library will not use function
@@ -279,6 +349,8 @@
 .. [#pairtype]
    https://bitbucket.org/pypy/pypy/raw/default/rpython/tool/pairtype.py
 
+.. [#issue-5135] http://bugs.python.org/issue5135
+
 
 Copyright
 =========

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


More information about the Python-checkins mailing list