Resolve the open issue on ABC support, link the reference implementation.
This commit is contained in:
parent
c892960042
commit
cab76cf1f0
90
pep-0443.txt
90
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)
|
||||
<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
|
||||
=========
|
||||
|
|
Loading…
Reference in New Issue