diff --git a/pep-0443.txt b/pep-0443.txt index 0fbfffe2a..df758321a 100644 --- a/pep-0443.txt +++ b/pep-0443.txt @@ -8,7 +8,7 @@ Status: Draft 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 @@ Implementation Notes 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 @@ Based on the current ``pkgutil.simplegeneric`` implementation and 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) + + >>> Container.register(P) + + +Faced with ambiguity, ``@singledispatch`` refuses the temptation to +guess:: + + >>> @singledispatch + ... def g(arg): + ... return "base" + ... + >>> g.register(Iterable, lambda arg: "iterable") + at 0x108b49110> + >>> g.register(Container, lambda arg: "container") + at 0x108b491c8> + >>> g(P()) + Traceback (most recent call last): + ... + RuntimeError: Ambiguous dispatch: + or + +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 @@ this PEP and providing initial feedback. 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 @@ References .. [#pairtype] https://bitbucket.org/pypy/pypy/raw/default/rpython/tool/pairtype.py +.. [#issue-5135] http://bugs.python.org/issue5135 + Copyright =========