Use C3-based linearization for ABC support to improve predictability

This commit is contained in:
Łukasz Langa 2013-07-01 14:46:15 +02:00
parent 6ec9fdf432
commit feef345e1e
1 changed files with 41 additions and 32 deletions

View File

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