<div dir="ltr">Previously I posted PEP 560 two weeks ago, while several other PEPs were also posted, so it didn't get much of attention. Here I post the PEP 560 again, now including the full text for convenience of commenting.<br><br>--<br>Ivan<br><br>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++<br><br>PEP: 560<br>Title: Core support for generic types<br>Author: Ivan Levkivskyi <<a href="mailto:levkivskyi@gmail.com">levkivskyi@gmail.com</a>><br>Status: Draft<br>Type: Standards Track<br>Content-Type: text/x-rst<br>Created: 03-Sep-2017<br>Python-Version: 3.7<br>Post-History: 09-Sep-2017<br><br><br>Abstract<br>========<br><br>Initially PEP 484 was designed in such way that it would not introduce<br>*any* changes to the core CPython interpreter. Now type hints and<br>the ``typing`` module are extensively used by the community, e.g. PEP 526<br>and PEP 557 extend the usage of type hints, and the backport of ``typing``<br>on PyPI has 1M downloads/month. Therefore, this restriction can be removed.<br>It is proposed to add two special methods ``__class_getitem__`` and<br>``__subclass_base__`` to the core CPython for better support of<br>generic types.<br><br><br>Rationale<br>=========<br><br>The restriction to not modify the core CPython interpreter lead to some<br>design decisions that became questionable when the ``typing`` module started<br>to be widely used. There are three main points of concerns:<br>performance of the ``typing`` module, metaclass conflicts, and the large<br>number of hacks currently used in ``typing``.<br><br><br>Performance:<br>------------<br><br>The ``typing`` module is one of the heaviest and slowest modules in<br>the standard library even with all the optimizations made. Mainly this is<br>because subscripted generic types (see PEP 484 for definition of terms<br>used in this PEP) are class objects (see also [1]_). The three main ways how<br>the performance can be improved with the help of the proposed special methods:<br><br>- Creation of generic classes is slow since the ``GenericMeta.__new__`` is<br>  very slow; we will not need it anymore.<br><br>- Very long MROs for generic classes will be twice shorter; they are present<br>  because we duplicate the ``collections.abc`` inheritance chain<br>  in ``typing``.<br><br>- Time of instantiation of generic classes will be improved<br>  (this is minor however).<br><br><br>Metaclass conflicts:<br>--------------------<br><br>All generic types are instances of ``GenericMeta``, so if a user uses<br>a custom metaclass, then it is hard to make a corresponding class generic.<br>This is particularly hard for library classes that a user doesn't control.<br>A workaround is to always mix-in ``GenericMeta``::<br><br>  class AdHocMeta(GenericMeta, LibraryMeta):<br>      pass<br><br>  class UserClass(LibraryBase, Generic[T], metaclass=AdHocMeta):<br>      ...<br><br>but this is not always practical or even possible. With the help of the<br>proposed special attributes the ``GenericMeta`` metaclass will not be needed.<br><br><br>Hacks and bugs that will be removed by this proposal:<br>-----------------------------------------------------<br><br>- ``_generic_new`` hack that exists since ``__init__`` is not called on<br>  instances with a type differing form the type whose ``__new__`` was called,<br>  ``C[int]().__class__ is C``.<br><br>- ``_next_in_mro`` speed hack will be not necessary since subscription will<br>  not create new classes.<br><br>- Ugly ``sys._getframe`` hack, this one is particularly nasty, since it looks<br>  like we can't remove it without changes outside ``typing``.<br><br>- Currently generics do dangerous things with private ABC caches<br>  to fix large memory consumption that grows at least as O(N\ :sup:`2`),<br>  see [2]_. This point is also important because it was recently proposed to<br>  re-implement ``ABCMeta`` in C.<br><br>- Problems with sharing attributes between subscripted generics,<br>  see [3]_. Current solution already uses ``__getattr__`` and ``__setattr__``,<br>  but it is still incomplete, and solving this without the current proposal<br>  will be hard and will need ``__getattribute__``.<br><br>- ``_no_slots_copy`` hack, where we clean-up the class dictionary on every<br>  subscription thus allowing generics with ``__slots__``.<br><br>- General complexity of the ``typing`` module, the new proposal will not<br>  only allow to remove the above mentioned hacks/bugs, but also simplify<br>  the implementation, so that it will be easier to maintain.<br><br><br>Specification<br>=============<br><br>The idea of ``__class_getitem__`` is simple: it is an exact analog of<br>``__getitem__`` with an exception that it is called on a class that<br>defines it, not on its instances, this allows us to avoid<br>``GenericMeta.__getitem__`` for things like ``Iterable[int]``.<br>The ``__class_getitem__`` is automatically a class method and<br>does not require ``@classmethod`` decorator (similar to<br>``__init_subclass__``) and is inherited like normal attributes.<br>For example::<br><br>  class MyList:<br>      def __getitem__(self, index):<br>          return index + 1<br>      def __class_getitem__(cls, item):<br>          return f"{cls.__name__}[{item.__name__}]"<br><br>  class MyOtherList(MyList):<br>      pass<br><br>  assert MyList()[0] == 1<br>  assert MyList[int] == "MyList[int]"<br><br>  assert MyOtherList()[0] == 1<br>  assert MyOtherList[int] == "MyOtherList[int]"<br><br>Note that this method is used as a fallback, so if a metaclass defines<br>``__getitem__``, then that will have the priority.<br><br>If an object that is not a class object appears in the bases of a class<br>definition, the ``__subclass_base__`` is searched on it. If found,<br>it is called with the original tuple of bases as an argument. If the result<br>of the call is not ``None``, then it is substituted instead of this object.<br>Otherwise (if the result is ``None``), the base is just removed. This is<br>necessary to avoid inconsistent MRO errors, that are currently prevented by<br>manipulations in ``GenericMeta.__new__``. After creating the class,<br>the original bases are saved in ``__orig_bases__`` (currently this is also<br>done by the metaclass).<br><br>NOTE: These two method names are reserved for exclusive use by<br>the ``typing`` module and the generic types machinery, and any other use is<br>strongly discouraged. The reference implementation (with tests) can be found<br>in [4]_, the proposal was originally posted and discussed on<br>the ``typing`` tracker, see [5]_.<br><br><br>Backwards compatibility and impact on users who don't use ``typing``:<br>=====================================================================<br><br>This proposal may break code that currently uses the names<br>``__class_getitem__`` and ``__subclass_base__``.<br><br>This proposal will support almost complete backwards compatibility with<br>the current public generic types API; moreover the ``typing`` module is still<br>provisional. The only two exceptions are that currently<br>``issubclass(List[int], List)`` returns True, with this proposal it will raise<br>``TypeError``. Also ``issubclass(collections.abc.Iterable, typing.Iterable)``<br>will return ``False``, which is probably desirable, since currently we have<br>a (virtual) inheritance cycle between these two classes.<br><br>With the reference implementation I measured negligible performance effects<br>(under 1% on a micro-benchmark) for regular (non-generic) classes.<br><br><br>References<br>==========<br><br>.. [1] Discussion following Mark Shannon's presentation at Language Summit<br>   (<a href="https://github.com/python/typing/issues/432">https://github.com/python/typing/issues/432</a>)<br><br>.. [2] Pull Request to implement shared generic ABC caches<br>   (<a href="https://github.com/python/typing/pull/383">https://github.com/python/typing/pull/383</a>)<br><br>.. [3] An old bug with setting/accessing attributes on generic types<br>   (<a href="https://github.com/python/typing/issues/392">https://github.com/python/typing/issues/392</a>)<br><br>.. [4] The reference implementation<br>   (<a href="https://github.com/ilevkivskyi/cpython/pull/2/files">https://github.com/ilevkivskyi/cpython/pull/2/files</a>)<br><br>.. [5] Original proposal<br>   (<a href="https://github.com/python/typing/issues/468">https://github.com/python/typing/issues/468</a>)<br><br><br>Copyright<br>=========<br><br>This document has been placed in the public domain.</div>