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).
|
||||
|
||||
When a generic function implementation 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::
|
||||
dispatch algorithm switches to an extended form of C3 linearization,
|
||||
which includes the relevant ABCs in the MRO of the provided argument.
|
||||
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):
|
||||
"""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
|
||||
|
||||
In its most basic form, it returns the MRO for the given type::
|
||||
In its most basic form, this linearization returns the MRO for the given
|
||||
type::
|
||||
|
||||
>>> _compose_mro(dict, [])
|
||||
[<class 'dict'>, <class 'object'>]
|
||||
|
||||
When the haystack consists of ABCs that the specified type is a subclass
|
||||
of, they are inserted in a predictable order::
|
||||
When the second argument contains ABCs that the specified type is
|
||||
a subclass of, they are inserted in a predictable order::
|
||||
|
||||
>>> _compose_mro(dict, [Sized, MutableMapping, str,
|
||||
... Sequence, Iterable])
|
||||
[<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'>]
|
||||
|
||||
While this mode of operation is significantly slower, all dispatch
|
||||
decisions are cached. The cache is invalidated on registering new
|
||||
implementations on the generic function or when user code calls
|
||||
``register()`` on an ABC to register a new virtual subclass. In the
|
||||
latter case, it is possible to create a situation with ambiguous
|
||||
dispatch, for instance::
|
||||
``register()`` on an ABC to implicitly subclass it. In the latter case,
|
||||
it is possible to create a situation with ambiguous dispatch, for
|
||||
instance::
|
||||
|
||||
>>> from collections import Iterable, Container
|
||||
>>> class P:
|
||||
|
@ -261,20 +250,38 @@ guess::
|
|||
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::
|
||||
Note that this exception would not be raised if one or more ABCs had
|
||||
been provided explicitly 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)
|
||||
... return value in range(10)
|
||||
...
|
||||
>>> g(Ten())
|
||||
'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
|
||||
==============
|
||||
|
@ -378,6 +385,8 @@ References
|
|||
a particular annotation style".
|
||||
(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/
|
||||
|
||||
.. [#peak-rules] http://peak.telecommunity.com/DevCenter/PEAK_2dRules
|
||||
|
|
Loading…
Reference in New Issue