![](https://secure.gravatar.com/avatar/337fb6df3cf57f5b7f47e573f1558cfa.jpg?s=120&d=mm&r=g)
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. -- Ivan ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ PEP: 560 Title: Core support for generic types Author: Ivan Levkivskyi <levkivskyi@gmail.com> Status: Draft Type: Standards Track Content-Type: text/x-rst Created: 03-Sep-2017 Python-Version: 3.7 Post-History: 09-Sep-2017 Abstract ======== Initially PEP 484 was designed in such way that it would not introduce *any* changes to the core CPython interpreter. Now type hints and 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`` on PyPI has 1M downloads/month. Therefore, this restriction can be removed. It is proposed to add two special methods ``__class_getitem__`` and ``__subclass_base__`` to the core CPython for better support of generic types. Rationale ========= The restriction to not modify the core CPython interpreter lead to some design decisions that became questionable when the ``typing`` module started to be widely used. There are three main points of concerns: performance of the ``typing`` module, metaclass conflicts, and the large number of hacks currently used in ``typing``. Performance: ------------ The ``typing`` module is one of the heaviest and slowest modules in the standard library even with all the optimizations made. Mainly this is because subscripted generic types (see PEP 484 for definition of terms used in this PEP) are class objects (see also [1]_). The three main ways how the performance can be improved with the help of the proposed special methods: - Creation of generic classes is slow since the ``GenericMeta.__new__`` is very slow; we will not need it anymore. - Very long MROs for generic classes will be twice shorter; they are present because we duplicate the ``collections.abc`` inheritance chain in ``typing``. - Time of instantiation of generic classes will be improved (this is minor however). Metaclass conflicts: -------------------- All generic types are instances of ``GenericMeta``, so if a user uses a custom metaclass, then it is hard to make a corresponding class generic. This is particularly hard for library classes that a user doesn't control. A workaround is to always mix-in ``GenericMeta``:: class AdHocMeta(GenericMeta, LibraryMeta): pass class UserClass(LibraryBase, Generic[T], metaclass=AdHocMeta): ... but this is not always practical or even possible. With the help of the proposed special attributes the ``GenericMeta`` metaclass will not be needed. Hacks and bugs that will be removed by this proposal: ----------------------------------------------------- - ``_generic_new`` hack that exists since ``__init__`` is not called on instances with a type differing form the type whose ``__new__`` was called, ``C[int]().__class__ is C``. - ``_next_in_mro`` speed hack will be not necessary since subscription will not create new classes. - Ugly ``sys._getframe`` hack, this one is particularly nasty, since it looks like we can't remove it without changes outside ``typing``. - Currently generics do dangerous things with private ABC caches to fix large memory consumption that grows at least as O(N\ :sup:`2`), see [2]_. This point is also important because it was recently proposed to re-implement ``ABCMeta`` in C. - Problems with sharing attributes between subscripted generics, see [3]_. Current solution already uses ``__getattr__`` and ``__setattr__``, but it is still incomplete, and solving this without the current proposal will be hard and will need ``__getattribute__``. - ``_no_slots_copy`` hack, where we clean-up the class dictionary on every subscription thus allowing generics with ``__slots__``. - General complexity of the ``typing`` module, the new proposal will not only allow to remove the above mentioned hacks/bugs, but also simplify the implementation, so that it will be easier to maintain. Specification ============= The idea of ``__class_getitem__`` is simple: it is an exact analog of ``__getitem__`` with an exception that it is called on a class that defines it, not on its instances, this allows us to avoid ``GenericMeta.__getitem__`` for things like ``Iterable[int]``. The ``__class_getitem__`` is automatically a class method and does not require ``@classmethod`` decorator (similar to ``__init_subclass__``) and is inherited like normal attributes. For example:: class MyList: def __getitem__(self, index): return index + 1 def __class_getitem__(cls, item): return f"{cls.__name__}[{item.__name__}]" class MyOtherList(MyList): pass assert MyList()[0] == 1 assert MyList[int] == "MyList[int]" assert MyOtherList()[0] == 1 assert MyOtherList[int] == "MyOtherList[int]" Note that this method is used as a fallback, so if a metaclass defines ``__getitem__``, then that will have the priority. If an object that is not a class object appears in the bases of a class definition, the ``__subclass_base__`` is searched on it. If found, 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. Otherwise (if the result is ``None``), the base is just removed. This is necessary to avoid inconsistent MRO errors, that are currently prevented by manipulations in ``GenericMeta.__new__``. After creating the class, the original bases are saved in ``__orig_bases__`` (currently this is also done by the metaclass). NOTE: These two method names are reserved for exclusive use by the ``typing`` module and the generic types machinery, and any other use is strongly discouraged. The reference implementation (with tests) can be found in [4]_, the proposal was originally posted and discussed on the ``typing`` tracker, see [5]_. Backwards compatibility and impact on users who don't use ``typing``: ===================================================================== This proposal may break code that currently uses the names ``__class_getitem__`` and ``__subclass_base__``. This proposal will support almost complete backwards compatibility with the current public generic types API; moreover the ``typing`` module is still provisional. The only two exceptions are that currently ``issubclass(List[int], List)`` returns True, with this proposal it will raise ``TypeError``. Also ``issubclass(collections.abc.Iterable, typing.Iterable)`` will return ``False``, which is probably desirable, since currently we have a (virtual) inheritance cycle between these two classes. With the reference implementation I measured negligible performance effects (under 1% on a micro-benchmark) for regular (non-generic) classes. References ========== .. [1] Discussion following Mark Shannon's presentation at Language Summit (https://github.com/python/typing/issues/432) .. [2] Pull Request to implement shared generic ABC caches (https://github.com/python/typing/pull/383) .. [3] An old bug with setting/accessing attributes on generic types (https://github.com/python/typing/issues/392) .. [4] The reference implementation (https://github.com/ilevkivskyi/cpython/pull/2/files) .. [5] Original proposal (https://github.com/python/typing/issues/468) Copyright ========= This document has been placed in the public domain.
![](https://secure.gravatar.com/avatar/d6b9415353e04ffa6de5a8f3aaea0553.jpg?s=120&d=mm&r=g)
On 9/27/2017 5:28 AM, Ivan Levkivskyi wrote:
Abstract ========
Initially PEP 484 was designed in such way that it would not introduce *any* changes to the core CPython interpreter. Now type hints and 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`` on PyPI has 1M downloads/month. Therefore, this restriction can be removed.
It seem sensible to me that you waited awhile to discover what would be needed.
It is proposed to add two special methods ``__class_getitem__`` and ``__subclass_base__`` to the core CPython for better support of generic types.
I would not be concerned about anyone (mis)using reserved words. If the new methods were for general use, I would question making them automatically class methods. Having __new__ automatically being a static method is convenient, but occasionally throws people off. But if they were only used for typing, perhaps it is ok. On the other hand, I expect that others will use __class_getitem__ for the same purpose -- to avoid defining a metaclass just to make class[something] work. So I question defining that as 'typing only'. Without rereading the PEP, the use case for __subclass_base__ is not clear to me. So I don't know if there are other uses for it. -- Terry Jan Reedy
![](https://secure.gravatar.com/avatar/337fb6df3cf57f5b7f47e573f1558cfa.jpg?s=120&d=mm&r=g)
On 27 September 2017 at 18:08, Terry Reedy <tjreedy@udel.edu> wrote:
On 9/27/2017 5:28 AM, Ivan Levkivskyi wrote:
It is proposed to add two special methods ``__class_getitem__`` and
``__subclass_base__`` to the core CPython for better support of generic types.
I would not be concerned about anyone (mis)using reserved words.
These methods are quite specific (especially __subclass_base__) so there are two points: * I would like to say that there are less backwards compatibility guarantees. Only the documented use is guaranteed to be backwards compatible, which is in this case Iterable[int] etc. * I don't want to "advertise" these methods. I could imagine someone will be unpleasantly surprised when finding these while reading someone other's code.
If the new methods were for general use, I would question making them automatically class methods. Having __new__ automatically being a static method is convenient, but occasionally throws people off. But if they were only used for typing, perhaps it is ok. On the other hand, I expect that others will use __class_getitem__ for the same purpose -- to avoid defining a metaclass just to make class[something] work. So I question defining that as 'typing only'.
I think we would rather want to limit the number of use cases for SomeClass[int], so that one doesn't need to guess if it is a generic class or something else.
Without rereading the PEP, the use case for __subclass_base__ is not clear to me. So I don't know if there are other uses for it.
The __subclass_base__ method is needed to avoid making the result of Iterable[int] a class object. Creating new class objects on every subscription is too expensive. However, these objects must be subclassable, so that the only way is to introduce this new method. For example: class Iterable: def __class_getitem__(cls, item): return GenericAlias(cls, item) class GenericAlias: def __init__(self, origin, item): self.origin = origin self.item = item def __subclass_base__(self, bases): return self.origin class MyIterable(Iterable[int]): ... Real code will be more complex, but this illustrates the idea. I don't know other use cases where one would want to allow non-classes in base classes list. Thanks for comments! -- Ivan
![](https://secure.gravatar.com/avatar/f3ba3ecffd20251d73749afbfa636786.jpg?s=120&d=mm&r=g)
On 27 September 2017 at 19:28, Ivan Levkivskyi <levkivskyi@gmail.com> wrote:
If an object that is not a class object appears in the bases of a class definition, the ``__subclass_base__`` is searched on it. If found, 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. Otherwise (if the result is ``None``), the base is just removed. This is necessary to avoid inconsistent MRO errors, that are currently prevented by manipulations in ``GenericMeta.__new__``. After creating the class, the original bases are saved in ``__orig_bases__`` (currently this is also done by the metaclass).
The name of "__subclass_base__" is still the part of this proposal that bothers me the most. I do know what it means, but "the class to use when this is listed as a base class for a subclass" is a genuinely awkward noun phrase. How would you feel about calling it "__mro_entry__", as a mnemonic for "the substitute entry to use instead of this object when calculating a subclass MRO"? Then the MRO calculation process would be: * generate "resolved_bases" from "orig_bases" (if any of them define "__mro_entry__") * use the existing MRO calculation process with resolved_bases as the input instead of orig_bases I think the other thing that needs to be clarified is whether or not the actual metaclass can expect to receive an already-resolved sequence of MRO entries as its list of bases, or if it will need to repeat the base resolution process executed while figuring out the metaclass. You can see the implications of that question most clearly when looking at the dynamic type creation API in the types module, where we offer both: # All inclusive with callback types.new_class(name, bases, kwds, exec_body) # Separate preparation phase mcl, ns, updated_kwds = types.prepare_class(name, bases, kwds) exec_body(ns) mcl(name, bases, ns, **updated_kwds) If we expect the metaclass to receive an already resolved sequence of bases, then we'll need to update the usage expectations for the latter API to look something like: mcl, ns, updated_kwds = types.prepare_class(name, bases, kwds) resolved_bases = ns.pop("__resolved_bases__", bases) # We leave ns["__orig_bases__"] set exec_body(ns) mcl(name, resolved_bases, ns, **updated_kwds) By contrast, if we decide that we're going do the full MRO resolution twice, then every metaclass will need to be updated to resolve the bases correctly (and make sure to use "cls.__bases__" or "cls.__mro__" after call up to their parent metaclass to actually create the class object). Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
![](https://secure.gravatar.com/avatar/337fb6df3cf57f5b7f47e573f1558cfa.jpg?s=120&d=mm&r=g)
On 28 September 2017 at 08:27, Nick Coghlan <ncoghlan@gmail.com> wrote:
On 27 September 2017 at 19:28, Ivan Levkivskyi <levkivskyi@gmail.com> wrote:
If an object that is not a class object appears in the bases of a class definition, the ``__subclass_base__`` is searched on it. If found, 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. Otherwise (if the result is ``None``), the base is just removed. This is necessary to avoid inconsistent MRO errors, that are currently prevented by manipulations in ``GenericMeta.__new__``. After creating the class, the original bases are saved in ``__orig_bases__`` (currently this is also done by the metaclass).
How would you feel about calling it "__mro_entry__", as a mnemonic for "the substitute entry to use instead of this object when calculating a subclass MRO"?
I don't have any preferences for the name, __mro_entry__ sounds equally OK to me. I think the other thing that needs to be clarified is whether or not
the actual metaclass can expect to receive an already-resolved sequence of MRO entries as its list of bases, or if it will need to repeat the base resolution process executed while figuring out the metaclass.
There are three points for discussion here: 1) It is necessary to make the bases resolution soon, before the metaclass is calculated. This is why I do this at the beginning of __build_class__ in the reference implementation. 2) Do we need to update type.__new__ to be able to accept non-classes as bases? I think no. One might be a bit surprised that class C(Iterable[int]): pass works, but type('C', (Iterable[int],), {}) fails with a metaclass conflict, but I think it is natural that static typing and dynamic class creation should not be used together. I propose to update ``type.__new__`` to just give a better error message explaining this. 3) Do we need to update types.new_class and types.prepare_class? Here I am not sure. These functions are rather utility functions and are designed to mimic in Python what __build_class__ does in C. I think we might add types._update_bases that does the same as its C counterpart. Then we can update types.new_class and types.prepare_class like you proposed, this will preserve their current API while types.new_class will match behaviour of __build_class__ If you and others agree with this, then I will update the PEP text and the reference implementation. Thanks for comments! -- Ivan
![](https://secure.gravatar.com/avatar/f3ba3ecffd20251d73749afbfa636786.jpg?s=120&d=mm&r=g)
On 29 September 2017 at 08:04, Ivan Levkivskyi <levkivskyi@gmail.com> wrote:
On 28 September 2017 at 08:27, Nick Coghlan <ncoghlan@gmail.com> wrote:
On 27 September 2017 at 19:28, Ivan Levkivskyi <levkivskyi@gmail.com> wrote:
If an object that is not a class object appears in the bases of a class definition, the ``__subclass_base__`` is searched on it. If found, 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. Otherwise (if the result is ``None``), the base is just removed. This is necessary to avoid inconsistent MRO errors, that are currently prevented by manipulations in ``GenericMeta.__new__``. After creating the class, the original bases are saved in ``__orig_bases__`` (currently this is also done by the metaclass).
How would you feel about calling it "__mro_entry__", as a mnemonic for "the substitute entry to use instead of this object when calculating a subclass MRO"?
I don't have any preferences for the name, __mro_entry__ sounds equally OK to me.
I'd propose changing it then, as searching for "Python mro entry" is likely to get people to the right place faster than searching for "Python subclass base".
I think the other thing that needs to be clarified is whether or not the actual metaclass can expect to receive an already-resolved sequence of MRO entries as its list of bases, or if it will need to repeat the base resolution process executed while figuring out the metaclass.
There are three points for discussion here:
1) It is necessary to make the bases resolution soon, before the metaclass is calculated. This is why I do this at the beginning of __build_class__ in the reference implementation.
Indeed.
2) Do we need to update type.__new__ to be able to accept non-classes as bases? I think no. One might be a bit surprised that
class C(Iterable[int]): pass
works, but
type('C', (Iterable[int],), {})
fails with a metaclass conflict, but I think it is natural that static typing and dynamic class creation should not be used together. I propose to update ``type.__new__`` to just give a better error message explaining this.
+1 from me, since that avoids ever resolving the list of bases twice.
3) Do we need to update types.new_class and types.prepare_class? Here I am not sure. These functions are rather utility functions and are designed to mimic in Python what __build_class__ does in C. I think we might add types._update_bases that does the same as its C counterpart. Then we can update types.new_class and types.prepare_class like you proposed, this will preserve their current API while types.new_class will match behaviour of __build_class__
Your suggestion for `types.__new__` gave me a different idea: what if `types.prepare_class` *also* just raised an error when given a non-class as a nominal base class? Then if we added `types.resolve_bases` as a public API, a full reimplementation of `types.new_class` would now look like: resolved_bases = types.resolve_bases(bases) mcl, ns, updated_kwds = types.prepare_class(name, resolved_bases, kwds) exec_body(ns) ns["__orig_bases__"] = bases mcl(name, resolved_bases, ns, **updated_kwds) That way, `types.new_class` would transparently switch to the new behaviour, while clients of any other dynamic type creation API could do their own base class resolution, even if the type creation API they were using didn't implicitly support MRO entry resolution. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
![](https://secure.gravatar.com/avatar/337fb6df3cf57f5b7f47e573f1558cfa.jpg?s=120&d=mm&r=g)
On 29 September 2017 at 08:57, Nick Coghlan <ncoghlan@gmail.com> wrote:
On 29 September 2017 at 08:04, Ivan Levkivskyi <levkivskyi@gmail.com> wrote:
How would you feel about calling it "__mro_entry__", as a mnemonic for "the substitute entry to use instead of this object when calculating a subclass MRO"?
I don't have any preferences for the name, __mro_entry__ sounds equally OK to me.
I'd propose changing it then, as searching for "Python mro entry" is likely to get people to the right place faster than searching for "Python subclass base".
OK, will do.
I propose to update ``type.__new__`` to just give a better error message explaining this.
+1 from me, since that avoids ever resolving the list of bases twice.
OK, I will update the reference implementation.
3) Do we need to update types.new_class and types.prepare_class? Here I am not sure. These functions are rather utility functions and are designed to mimic in Python what __build_class__ does in C. I think we might add types._update_bases that does the same as its C counterpart. Then we can update types.new_class and types.prepare_class like you proposed, this will preserve their current API while types.new_class will match behaviour of __build_class__
Your suggestion for `types.__new__` gave me a different idea: what if `types.prepare_class` *also* just raised an error when given a non-class as a nominal base class?
Then if we added `types.resolve_bases` as a public API, a full reimplementation of `types.new_class` would now look like:
resolved_bases = types.resolve_bases(bases) mcl, ns, updated_kwds = types.prepare_class(name, resolved_bases, kwds) exec_body(ns) ns["__orig_bases__"] = bases mcl(name, resolved_bases, ns, **updated_kwds)
That way, `types.new_class` would transparently switch to the new behaviour, while clients of any other dynamic type creation API could do their own base class resolution, even if the type creation API they were using didn't implicitly support MRO entry resolution.
Yes, makes sense. I no one is against adding new public ``types.resolve_bases`` then I will add this to the PEP. -- Ivan
![](https://secure.gravatar.com/avatar/047f2332cde3730f1ed661eebb0c5686.jpg?s=120&d=mm&r=g)
Hey Ivan, There seem to be some action items from this thread that I haven't seen reflected in the PEP source code yet. - Change the title (I like "Core support for typing module and generic types" but maybe it can be shortened to "Core support for generics in the typing module" -- if you like that, go for it) - Change __subclass_base__ to __mro_entry__ - Add types.resolve_bases and related changes - Maybe some clarifications summarizing the feedback from this thread (esp. Nick's)? Then the next step I propose is a PR with a full implementation. After that I'll likely approve the PEP (or we'll have minor feedback based on trying the implementation). I don't require a new typing.py that uses these features to be part of the "full implementation" but it would be nice to make a start with that as well -- if we can make the case that it will speed up "import typing" a lot then that would be a powerful argument for the PEP. -- --Guido van Rossum (python.org/~guido)
![](https://secure.gravatar.com/avatar/337fb6df3cf57f5b7f47e573f1558cfa.jpg?s=120&d=mm&r=g)
On 10 November 2017 at 17:43, Guido van Rossum <guido@python.org> wrote:
Hey Ivan,
There seem to be some action items from this thread that I haven't seen reflected in the PEP source code yet. [...snip...] Then the next step I propose is a PR with a full implementation. After that I'll likely approve the PEP (or we'll have minor feedback based on trying the implementation).
Yes, sorry, I wanted to make updates to the PEP and reference implementation, but last two weeks were very busy. Hopefully, I will work on it this weekend. -- Ivan
![](https://secure.gravatar.com/avatar/047f2332cde3730f1ed661eebb0c5686.jpg?s=120&d=mm&r=g)
On Fri, Nov 10, 2017 at 8:54 AM, Ivan Levkivskyi <levkivskyi@gmail.com> wrote:
On 10 November 2017 at 17:43, Guido van Rossum <guido@python.org> wrote:
There seem to be some action items from this thread that I haven't seen reflected in the PEP source code yet. [...snip...] Then the next step I propose is a PR with a full implementation. After that I'll likely approve the PEP (or we'll have minor feedback based on trying the implementation).
Yes, sorry, I wanted to make updates to the PEP and reference implementation, but last two weeks were very busy. Hopefully, I will work on it this weekend.
Thanks, I am happy now with the PEP, except for one detail: maybe `__mro_entry__` should always return a tuple and then maybe renamed to `__mro_entries__`. (See debate at https://github.com/python/peps/pull/460#issuecomment-343969528 .) Other than that I think we just need to satisfy a process nit: let's post the final PEP (after that issue is resolved) to python-dev. -- --Guido van Rossum (python.org/~guido)
![](https://secure.gravatar.com/avatar/f3ba3ecffd20251d73749afbfa636786.jpg?s=120&d=mm&r=g)
On 14 November 2017 at 09:41, Guido van Rossum <guido@python.org> wrote:
On Fri, Nov 10, 2017 at 8:54 AM, Ivan Levkivskyi <levkivskyi@gmail.com> wrote:
On 10 November 2017 at 17:43, Guido van Rossum <guido@python.org> wrote:
There seem to be some action items from this thread that I haven't seen reflected in the PEP source code yet. [...snip...] Then the next step I propose is a PR with a full implementation. After that I'll likely approve the PEP (or we'll have minor feedback based on trying the implementation).
Yes, sorry, I wanted to make updates to the PEP and reference implementation, but last two weeks were very busy. Hopefully, I will work on it this weekend.
Thanks, I am happy now with the PEP, except for one detail: maybe `__mro_entry__` should always return a tuple and then maybe renamed to `__mro_entries__`. (See debate at https://github.com/python/peps/pull/460#issuecomment-343969528 .)
I like that - very nice refinement. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
![](https://secure.gravatar.com/avatar/b8efb5dcb59ea1048c4763b7507d3343.jpg?s=120&d=mm&r=g)
On Tue, Nov 14, 2017 at 4:49 AM, Nick Coghlan <ncoghlan@gmail.com> wrote:
On 14 November 2017 at 09:41, Guido van Rossum <guido@python.org> wrote:
Thanks, I am happy now with the PEP, except for one detail: maybe `__mro_entry__` should always return a tuple and then maybe renamed to `__mro_entries__`. (See debate at https://github.com/python/peps/pull/460#issuecomment-343969528 .)
I like that - very nice refinement.
I hope the order in which multiple __mro_entries__ will appear in the mro will be documented clearly, regardless of how obvious it might feel. It might take a while, before anyone notices that something weird happens because they did it the wrong way around. Out of curiosity, what kind of cases would benefit from __mro__entries__ being able to return two or more entries? Also, I'm still wondering about __bases__ and __orig_bases__. Could we call these __concrete_bases__ and __bases__ instead (respectively)? For an explanation of why I think this might be a good idea, see this new thread: https://mail.python.org/pipermail/python-ideas/2017-November/047896.html ––Koos -- + Koos Zevenhoven + http://twitter.com/k7hoven +
![](https://secure.gravatar.com/avatar/337fb6df3cf57f5b7f47e573f1558cfa.jpg?s=120&d=mm&r=g)
On 29 September 2017 at 10:14, Victor Stinner <victor.stinner@gmail.com> wrote:
Title: Core support for generic types
Would it be possible to mention "typing" somewhere in the title? If you don't know the context, it's hard to understand that the PEP is related to type annotation and type checks. At least just from the title.
What do you think about "Core support for typing module and generic types"? Another option is "Runtime mechanism to improve generics and typing module". -- Ivan
![](https://secure.gravatar.com/avatar/b8efb5dcb59ea1048c4763b7507d3343.jpg?s=120&d=mm&r=g)
On Wed, Sep 27, 2017 at 12:28 PM, Ivan Levkivskyi <levkivskyi@gmail.com> wrote:
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.
[..]
After creating the class, the original bases are saved in ``__orig_bases__`` (currently this is also done by the metaclass).
Those are *still* bases, right, even if they are not in the mro? I'm not sure if this is a naming thing or something even more.
NOTE: These two method names are reserved for exclusive use by the ``typing`` module and the generic types machinery, and any other use is strongly discouraged.
Given the situation, that may be a good thing. But will it really work? I think it is also strongly discouraged to invent your own dunder method names, but people still do it.
The reference implementation (with tests) can be found in [4]_, the proposal was originally posted and discussed on the ``typing`` tracker, see [5]_.
Backwards compatibility and impact on users who don't use ``typing``: =====================================================================
This proposal may break code that currently uses the names ``__class_getitem__`` and ``__subclass_base__``.
––Koos [..] -- + Koos Zevenhoven + http://twitter.com/k7hoven +
![](https://secure.gravatar.com/avatar/337fb6df3cf57f5b7f47e573f1558cfa.jpg?s=120&d=mm&r=g)
On 10 November 2017 at 18:39, Koos Zevenhoven <k7hoven@gmail.com> wrote:
On Wed, Sep 27, 2017 at 12:28 PM, Ivan Levkivskyi <levkivskyi@gmail.com> wrote:
After creating the class, the original bases are saved in ``__orig_bases__`` (currently this is also done by the metaclass).
Those are *still* bases, right, even if they are not in the mro? I'm not sure if this is a naming thing or something even more.
The objects that have __subclass_base__ method (proposed to rename to __mro_entry__) are removed from __bases__ attributed of the newly created class. Otherwise they may cause a metaclass conflict. One can however still call them syntactic (or static?) bases. For example this is how it is going to be used by typing: from typing import List class Tokens(List[int]): ... assert Tokens.__bases__ == (list,)
NOTE: These two method names are reserved for exclusive use by
the ``typing`` module and the generic types machinery, and any other use is strongly discouraged.
Given the situation, that may be a good thing. But will it really work? I think it is also strongly discouraged to invent your own dunder method names, but people still do it.
Terry had a similar comment. I will "soften" this formulation in the next revision of the PEP. -- Ivan
![](https://secure.gravatar.com/avatar/b8efb5dcb59ea1048c4763b7507d3343.jpg?s=120&d=mm&r=g)
On Fri, Nov 10, 2017 at 8:33 PM, Ivan Levkivskyi <levkivskyi@gmail.com> wrote:
On 10 November 2017 at 18:39, Koos Zevenhoven <k7hoven@gmail.com> wrote:
On Wed, Sep 27, 2017 at 12:28 PM, Ivan Levkivskyi <levkivskyi@gmail.com> wrote:
After creating the class, the original bases are saved in ``__orig_bases__`` (currently this is also done by the metaclass).
Those are *still* bases, right, even if they are not in the mro? I'm not sure if this is a naming thing or something even more.
The objects that have __subclass_base__ method (proposed to rename to __mro_entry__) are removed from __bases__ attributed of the newly created class. Otherwise they may cause a metaclass conflict. One can however still call them syntactic (or static?) bases. For example this is how it is going to be used by typing:
from typing import List
class Tokens(List[int]): ...
assert Tokens.__bases__ == (list,)
Why is List[int] not allowed to be the base? Neither method-lookup performance nor the metaclass conflict issue seem to depend on whether List[int] is in __bases__.
NOTE: These two method names are reserved for exclusive use by
the ``typing`` module and the generic types machinery, and any other use is strongly discouraged.
Given the situation, that may be a good thing. But will it really work? I think it is also strongly discouraged to invent your own dunder method names, but people still do it.
Terry had a similar comment. I will "soften" this formulation in the next revision of the PEP.
Right, I assume you mean the one where he pointed out that implicitly turning the methods into staticmethods based on their names makes those names reserved words. -- Koos -- + Koos Zevenhoven + http://twitter.com/k7hoven +
![](https://secure.gravatar.com/avatar/337fb6df3cf57f5b7f47e573f1558cfa.jpg?s=120&d=mm&r=g)
On 10 November 2017 at 21:19, Koos Zevenhoven <k7hoven@gmail.com> wrote:
On Fri, Nov 10, 2017 at 8:33 PM, Ivan Levkivskyi <levkivskyi@gmail.com> wrote:
On 10 November 2017 at 18:39, Koos Zevenhoven <k7hoven@gmail.com> wrote:
On Wed, Sep 27, 2017 at 12:28 PM, Ivan Levkivskyi <levkivskyi@gmail.com> wrote:
After creating the class, the original bases are saved in ``__orig_bases__`` (currently this is also done by the metaclass).
Those are *still* bases, right, even if they are not in the mro? I'm not sure if this is a naming thing or something even more.
The objects that have __subclass_base__ method (proposed to rename to __mro_entry__) are removed from __bases__ attributed of the newly created class. Otherwise they may cause a metaclass conflict. One can however still call them syntactic (or static?) bases. For example this is how it is going to be used by typing:
from typing import List
class Tokens(List[int]): ...
assert Tokens.__bases__ == (list,)
Why is List[int] not allowed to be the base? Neither method-lookup performance nor the metaclass conflict issue seem to depend on whether List[int] is in __bases__.
The situation is actually quite opposite. Interestingly, the whole discussion started from Mark Shannon pointing to these problems with List[int] at the Language Summit. The original discussion on typing tracker is referenced in the PEP draft. -- Ivan
![](https://secure.gravatar.com/avatar/b8efb5dcb59ea1048c4763b7507d3343.jpg?s=120&d=mm&r=g)
On Fri, Nov 10, 2017 at 10:26 PM, Ivan Levkivskyi <levkivskyi@gmail.com> wrote:
On 10 November 2017 at 21:19, Koos Zevenhoven <k7hoven@gmail.com> wrote:
On Fri, Nov 10, 2017 at 8:33 PM, Ivan Levkivskyi <levkivskyi@gmail.com> wrote:
On 10 November 2017 at 18:39, Koos Zevenhoven <k7hoven@gmail.com> wrote:
On Wed, Sep 27, 2017 at 12:28 PM, Ivan Levkivskyi <levkivskyi@gmail.com
wrote:
After creating the class, the original bases are saved in ``__orig_bases__`` (currently this is also done by the metaclass).
Those are *still* bases, right, even if they are not in the mro? I'm not sure if this is a naming thing or something even more.
The objects that have __subclass_base__ method (proposed to rename to __mro_entry__) are removed from __bases__ attributed of the newly created class. Otherwise they may cause a metaclass conflict. One can however still call them syntactic (or static?) bases. For example this is how it is going to be used by typing:
from typing import List
class Tokens(List[int]): ...
assert Tokens.__bases__ == (list,)
Why is List[int] not allowed to be the base? Neither method-lookup performance nor the metaclass conflict issue seem to depend on whether List[int] is in __bases__.
The situation is actually quite opposite. Interestingly, the whole discussion started from Mark Shannon pointing to these problems with List[int] at the Language Summit. The original discussion on typing tracker is referenced in the PEP draft.
What do you mean? I don't see any mention of __bases__ in the discussion. Perhaps related, if the syntax indeed were like this (as suggested by Mark?): @implements(List[int]) class Tokens(list): ... then I would expect List[int] to be in __implements__ or something like that. But how to interpret that? Maybe something like "List describes the interface/API that list has"? -- Koos -- + Koos Zevenhoven + http://twitter.com/k7hoven +
participants (6)
-
Guido van Rossum
-
Ivan Levkivskyi
-
Koos Zevenhoven
-
Nick Coghlan
-
Terry Reedy
-
Victor Stinner