Resolve the open issue on ABC support, link the reference implementation.

This commit is contained in:
Łukasz Langa 2013-05-25 14:03:31 +02:00
parent c892960042
commit cab76cf1f0
1 changed files with 81 additions and 9 deletions

View File

@ -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)
<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 @@ 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
=========