Updates to PEP 560 (#460)

* Change title and use __mro_entry__

* Add more discussion and types.resolve_bases

* Improve example
This commit is contained in:
Ivan Levkivskyi 2017-11-11 01:35:15 +01:00 committed by Guido van Rossum
parent d85cbb9264
commit 4fd3245ea1
1 changed files with 53 additions and 12 deletions

View File

@ -1,5 +1,5 @@
PEP: 560 PEP: 560
Title: Core support for generic types Title: Core support for typing module and generic types
Author: Ivan Levkivskyi <levkivskyi@gmail.com> Author: Ivan Levkivskyi <levkivskyi@gmail.com>
Status: Draft Status: Draft
Type: Standards Track Type: Standards Track
@ -18,7 +18,7 @@ the ``typing`` module are extensively used by the community, e.g. PEP 526
and PEP 557 extend the usage of type hints, and the backport of ``typing`` and PEP 557 extend the usage of type hints, and the backport of ``typing``
on PyPI has 1M downloads/month. Therefore, this restriction can be removed. on PyPI has 1M downloads/month. Therefore, this restriction can be removed.
It is proposed to add two special methods ``__class_getitem__`` and It is proposed to add two special methods ``__class_getitem__`` and
``__subclass_base__`` to the core CPython for better support of ``__mro_entry__`` to the core CPython for better support of
generic types. generic types.
@ -134,31 +134,72 @@ For example::
Note that this method is used as a fallback, so if a metaclass defines Note that this method is used as a fallback, so if a metaclass defines
``__getitem__``, then that will have the priority. ``__getitem__``, then that will have the priority.
``__subclass_base__``
--------------------- ``__mro_entry__``
-----------------
If an object that is not a class object appears in the bases of a class If an object that is not a class object appears in the bases of a class
definition, then ``__subclass_base__`` is searched on it. If found, definition, then ``__mro_entry__`` is searched on it. If found,
it is called with the original tuple of bases as an argument. If the result it is called with the original tuple of bases as an argument. If the result
of the call is not ``None``, then it is substituted instead of this object. of the call is not ``None``, then it is substituted instead of this object.
Otherwise (if the result is ``None``), the base is just removed. This is Otherwise (if the result is ``None``), the base is just removed. This is
necessary to avoid inconsistent MRO errors, that are currently prevented by necessary to avoid inconsistent MRO errors, that are currently prevented by
manipulations in ``GenericMeta.__new__``. After creating the class, manipulations in ``GenericMeta.__new__``. After creating the class,
the original bases are saved in ``__orig_bases__`` (currently this is also the original bases are saved in ``__orig_bases__`` (currently this is also
done by the metaclass). done by the metaclass). For example::
NOTE: These two method names are reserved for exclusive use by class GenericAlias:
the ``typing`` module and the generic types machinery, and any other use is def __init__(self, origin, item):
strongly discouraged. The reference implementation (with tests) can be found self.origin = origin
in [4]_, and the proposal was originally posted and discussed on self.item = item
the ``typing`` tracker, see [5]_. def __mro_entry__(self, bases):
return self.origin
class NewList:
def __class_getitem__(cls, item):
return GenericAlias(cls, item)
class Tokens(NewList[int]):
...
assert Tokens.__bases__ == (NewList,)
assert Tokens.__orig_bases__ == (NewList[int],)
assert Tokens.__mro__ == (Tokens, NewList, object)
NOTE: These two method names are reserved for use by the ``typing`` module
and the generic types machinery, and any other use is discouraged.
The reference implementation (with tests) can be found in [4]_, and
the proposal was originally posted and discussed on the ``typing`` tracker,
see [5]_.
Dynamic class creation and ``types.resolve_bases``
--------------------------------------------------
``type.__new__`` will not perform any MRO entry resolution. So that a direct
call ``type('Tokens', (List[int],), {})`` will fail. This is done for
performance reasons and to minimize the number of implicit transformations.
Instead, a helper function ``resolve_bases`` will be added to
the ``types`` module to allow an explicit ``__mro_entry__`` resolution in
the context of dynamic class creation. Correspondingly, ``types.new_class``
will be updated to reflect the new class creation steps while maintaining
the backwards compatibility::
def new_class(name, bases=(), kwds=None, exec_body=None):
resolved_bases = resolve_bases(bases) # This step is added
meta, ns, kwds = prepare_class(name, resolved_bases, kwds)
if exec_body is not None:
exec_body(ns)
cls = meta(name, resolved_bases, ns, **kwds)
cls.__orig_bases__ = bases # This step is added
return cls
Backwards compatibility and impact on users who don't use ``typing`` Backwards compatibility and impact on users who don't use ``typing``
==================================================================== ====================================================================
This proposal may break code that currently uses the names This proposal may break code that currently uses the names
``__class_getitem__`` and ``__subclass_base__``. (But the language ``__class_getitem__`` and ``__mro_entry__``. (But the language
reference explicitly reserves *all* undocumented dunder names, and reference explicitly reserves *all* undocumented dunder names, and
allows "breakage without warning"; see [6]_.) allows "breakage without warning"; see [6]_.)