[Python-Dev] Comments on PEP 560 (Core support for typing module and generic types)

Mark Shannon mark at hotpy.org
Mon Nov 20 04:22:16 EST 2017



On 19/11/17 22:36, Ivan Levkivskyi wrote:
> On 19 November 2017 at 21:06, Mark Shannon <mark at hotpy.org 
> <mailto:mark at hotpy.org>> wrote:
> 
>     By far and away the largest change in PEP 560 is the change to the
>     behaviour of object.__getitem__. This is not mentioned in the PEP at
>     all, but is explicit in the draft implementation.
>     The implementation could implement `type.__getitem__` instead of
>     changing `object.__getitem__`, but that is still a major change to
>     the language.
> 
> 
> Except that there is no such thing as object._getitem__. Probably you 
> mean PyObject_GetItem (which is just what is done by BINARY_SUBSCR opcode).

Yes, I should have taken more time to look at the code. I thought you 
were implementing `object.__getitem__`.
In general, Python implements its operators as a simple redirection to a 
special method, with the exception of binary operators which are 
necessarily more complex.

f(...) ->  type(f).__call__(f, ...)
o.a -> type(o).__getattribute__(o, "a")
o[i] -> type(o).__getitem__(o, i)

Which is why I don't like the additional complexity you are adding to 
the dispatching. If we really must have `__class_getitem__` (and I don't 
think that we do) then implementing `type.__getitem__` is a much less 
intrusive way to do it.

> In fact, I initially implemented type.__getitem__, but I didn't like it 
> for various reasons.

Could you elaborate?

> 
> I don't think that any of the above are changes to the language. These 
> are rather implementation details. The only unusual thing is that while 
> dunders are
> searched on class, __class_getitem__ is searched on the object (class 
> object in this case) itself. But this is clearly explained in the PEP.
> 
>     In fact, the addition of `__mro_entries__` makes `__class_getitem__`
>     unnecessary.
> 
> 
> But how would you implement this:
> 
> class C(Generic[T]):
>      ...
> 
> C[int]  # This should work

The issue of type-hinting container classes is a tricky one. The 
definition is defining both the implementation class and the interface 
type. We want the implementation and interface to be distinct. However, 
we want to avoid needless repetition.

In the example you gave, `C` is a class definition that is intended to 
be used as a generic container. In my mind the cleanest way to do this 
is with a class decorator. Something like:

@Generic[T]
class C: ...

or

@implements(Generic[T])
class C: ...

C would then be a type not a class, as the decorator is free to return a 
non-class object.

It allows the implementation and interface to be distinct:

@implements(Sequence[T])
class MySeq(list): ...

@implements(Set[Node])
class SmallNodeSet(list): ...
     # For small sets a list is more efficient than a set.

but avoid repetition for the more common case:

class IntStack(List[int]): ...

Given the power and flexibility of the built-in data structures, 
defining custom containers is relatively rare. I'm not saying that it 
should not be considered, but a few minor hurdles are acceptable to keep 
the rest of the language (including more common uses of type-hints) clean.

> 
>     The name `__mro_entries__` suggests that this method is solely
>     related method resolution order, but it is really about providing an
>     instance of `type` where one is expected. This is analogous to
>     `__int__`, `__float__` and `__index__` which provide an int, float
>     and int respectively.
>     This rather suggests (to me at least) the name `__type__` instead of
>     `__mro_entries__`
> 
> 
> This was already discussed during months, and in particular the name 
> __type__ was not liked by ... you 

Ha, you have a better memory than I :) I won't make any more naming 
suggestions.
What I should have said is that the name should reflect what it does, 
not the initial reason for including it.

> https://github.com/python/typing/issues/432#issuecomment-304070379
> So I would propose to stop bikesheding this (also Guido seems to like 
> the currently proposed name).
> 
>     Should `isinstance` and `issubclass` call `__mro_entries__` before
>     raising an error if the second argument is not a class?
>     In other words, if `List` implements `__mro_entries__` to return
>     `list` then should `issubclass(x, List)` act like `issubclass(x, list)`?
>     (IMO, it shouldn't) The reasoning behind this decision should be
>     made explicit in the PEP.
> 
> 
> I think this is orthogonal to the PEP. There are many situations where a 
> class is expected,
> and IMO it is clear that all that are not mentioned in the PEP stay 
> unchanged.

Indeed, but you do mention issubclass in the PEP. I think a few extra 
words of explanation would be helpful.

Cheers,
Mark.


More information about the Python-Dev mailing list