Use C3-based linearization for ABC support to improve predictability
This commit is contained in:
parent
6ec9fdf432
commit
feef345e1e
73
pep-0443.txt
73
pep-0443.txt
|
@ -193,48 +193,37 @@ handling of old-style classes and Zope's ExtensionClasses. More
|
||||||
importantly, it introduces support for Abstract Base Classes (ABC).
|
importantly, it introduces support for Abstract Base Classes (ABC).
|
||||||
|
|
||||||
When a generic function implementation is registered for an ABC, the
|
When a generic function implementation is registered for an ABC, the
|
||||||
dispatch algorithm switches to a mode of MRO calculation for the
|
dispatch algorithm switches to an extended form of C3 linearization,
|
||||||
provided argument which includes the relevant ABCs. The algorithm is as
|
which includes the relevant ABCs in the MRO of the provided argument.
|
||||||
follows::
|
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):
|
In its most basic form, this linearization returns the MRO for the given
|
||||||
"""Calculates the MRO for a given class `cls`, including relevant
|
type::
|
||||||
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::
|
|
||||||
|
|
||||||
>>> _compose_mro(dict, [])
|
>>> _compose_mro(dict, [])
|
||||||
[<class 'dict'>, <class 'object'>]
|
[<class 'dict'>, <class 'object'>]
|
||||||
|
|
||||||
When the haystack consists of ABCs that the specified type is a subclass
|
When the second argument contains ABCs that the specified type is
|
||||||
of, they are inserted in a predictable order::
|
a subclass of, they are inserted in a predictable order::
|
||||||
|
|
||||||
>>> _compose_mro(dict, [Sized, MutableMapping, str,
|
>>> _compose_mro(dict, [Sized, MutableMapping, str,
|
||||||
... Sequence, Iterable])
|
... Sequence, Iterable])
|
||||||
[<class 'dict'>, <class 'collections.abc.MutableMapping'>,
|
[<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'>]
|
<class 'object'>]
|
||||||
|
|
||||||
While this mode of operation is significantly slower, all dispatch
|
While this mode of operation is significantly slower, all dispatch
|
||||||
decisions are cached. The cache is invalidated on registering new
|
decisions are cached. The cache is invalidated on registering new
|
||||||
implementations on the generic function or when user code calls
|
implementations on the generic function or when user code calls
|
||||||
``register()`` on an ABC to register a new virtual subclass. In the
|
``register()`` on an ABC to implicitly subclass it. In the latter case,
|
||||||
latter case, it is possible to create a situation with ambiguous
|
it is possible to create a situation with ambiguous dispatch, for
|
||||||
dispatch, for instance::
|
instance::
|
||||||
|
|
||||||
>>> from collections import Iterable, Container
|
>>> from collections import Iterable, Container
|
||||||
>>> class P:
|
>>> class P:
|
||||||
|
@ -261,20 +250,38 @@ guess::
|
||||||
RuntimeError: Ambiguous dispatch: <class 'collections.abc.Container'>
|
RuntimeError: Ambiguous dispatch: <class 'collections.abc.Container'>
|
||||||
or <class 'collections.abc.Iterable'>
|
or <class 'collections.abc.Iterable'>
|
||||||
|
|
||||||
Note that this exception would not be raised if ``Iterable`` and
|
Note that this exception would not be raised if one or more ABCs had
|
||||||
``Container`` had been provided as base classes during class definition.
|
been provided explicitly as base classes during class definition. In
|
||||||
In this case dispatch happens in the MRO order::
|
this case dispatch happens in the MRO order::
|
||||||
|
|
||||||
>>> class Ten(Iterable, Container):
|
>>> class Ten(Iterable, Container):
|
||||||
... def __iter__(self):
|
... def __iter__(self):
|
||||||
... for i in range(10):
|
... for i in range(10):
|
||||||
... yield i
|
... yield i
|
||||||
... def __contains__(self, value):
|
... def __contains__(self, value):
|
||||||
... return value in range(10)
|
... return value in range(10)
|
||||||
...
|
...
|
||||||
>>> g(Ten())
|
>>> g(Ten())
|
||||||
'iterable'
|
'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
|
Usage Patterns
|
||||||
==============
|
==============
|
||||||
|
@ -378,6 +385,8 @@ References
|
||||||
a particular annotation style".
|
a particular annotation style".
|
||||||
(http://www.python.org/dev/peps/pep-0008)
|
(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/
|
.. [#pep-3124] http://www.python.org/dev/peps/pep-3124/
|
||||||
|
|
||||||
.. [#peak-rules] http://peak.telecommunity.com/DevCenter/PEAK_2dRules
|
.. [#peak-rules] http://peak.telecommunity.com/DevCenter/PEAK_2dRules
|
||||||
|
|
Loading…
Reference in New Issue