Adding Type[C] support to PEP 484 and typing.py

There's one new feature I'd like to add to PEP 484 and the typing.py module that will go out with Python 3.5.2. There's a long discssion in https://github.com/python/typing/issues/107, but I think I can explain the proposal very quickly. There's currently no good way to talk about class objects. If you have a function argument that's a class (as opposed to an instance) the only annotation you can give it is `type`. But I've encountered this pattern quite frequently, and what people invariably want to say is "it's a subclass of this here class C." Example: class User: ... class TeamUser(User): ... class ProUser(User): ... def new_user(user_class: Type[User]) -> User: ... The proposal is to let you spell that explicitly by setting the type or such an argument to `Type[C]`. The type checker will then understand that if you call it, the result is an instance of C. It will also know about all the class methods defined on C. There are some subtleties, e.g. in the above example we would actually like to know that the return type varies with the argument type: joe = new_user(ProUser) # Really, joe is a ProUser, not just a User This can be done using a type variable, e.g. U = TypeVar('U', bound=User) def new_user(user_class: Type[U]) -> U: ... joe = new_user(ProUser) The key part I want to settle quickly is the notation: I need to add a special generic `class Type` to typing.py so that we can start implementing this in mypy. Any objections? Or should I just go ahead and add the text to PEP 484 and post the diff to python-dev? -- --Guido van Rossum (python.org/~guido)

Great. We'll be able to move this very quickly once we decide whether it should be called Type[C] or Class[C]. The original proposal is Type[C], and it's nice because it's a pun on `type`, just like Tuple[...] is a pun on `tuple` (and similar for List etc.). But OTOH it really annotates a class object, not an abstract type. I still prefer Type[C] -- anyone want to argue that we're making a mistake? (I know, nobody's listening, everyone's too busy arguing that Path should inherit from str. :-) -- --Guido van Rossum (python.org/~guido)

On Thu, May 12, 2016 at 8:00 PM, Chris Angelico <rosuav@gmail.com> wrote:
Well, actually, I call them class objects all the time... Plus PEP 484 (at least the recent revisions, though this has always been the BDFL-delegate's intention) tries to make a clear terminological between classes (the things you have at runtime) and types (the things that type checkers care about). There's a big overlap because most classes are also types -- but not the other way around! E.g. Any is a type but not a class (you can neither inherit from Any nor instantiate it), and the same is true for unions and type variables. And it's pretty clear that when you have an argument or variable annotated with Type[C] it has to be a real class object, not Any or a union or a type variable, because in the common use case, functions taking class objects either instantiate that class, call class methods on it, or use it for isinstance() checks, and none of those things would work if at run time they received Any or a union or type variable. Let's look at that function new_user() again: def new_user(user_class: Type[User]) -> User: return user_class() We can call new_user(ProUser) or new_user(TeamUser), but calling new_user(Any) isn't going to work at runtime, not us new_user(Union[ProUser, TeamUser]). Note also that this is a unique twist for Type[C] that doesn't exist for non-type annotations. If we have a function argument annotated with a class, e.g. def first_name(u: User) -> str: ... it's totally reasonable to call it with an argument whose type is Union[ProUser, TeamUser]: def get_user_by_id(id: str) -> Union[ProUser, TeamUser]: ... name = first_name(get_user_by_id('joe')) This is because here the *type* returned by get_user_by_id() is a union, but the actual *values* it returns are always instances of one of the runtime classes ProUser or TeamUser. If we had had more consistent terminology in Python from the start, this naming issue would never have come up -- we'd never have used type(x) as (almost) equivalent to x.__class__, and we wouldn't have used class C(type): ... to define a metaclass (i.e. a class's class). But that's water under the bridge, and we might a well write Type[C] rather than Class[C] to pub with `type`. And it seems only two people care about the difference -- Mark Shannon and myself. :-) So Type[C] it is. -- --Guido van Rossum (python.org/~guido)

On 05/12/2016 09:35 PM, Guido van Rossum wrote:
I definitely don't have enough CS background to understand the subtle distinction you're drawing here (at least, I hope it's subtle ;) . But to me, it's a type -- and when writing metaclasses, my decision for the name has always been between SomethingMeta or SomethingType, it's never been SomethingClass.
Cool. :) -- ~Ethan~

On 13 May 2016 at 12:52, Guido van Rossum <guido@python.org> wrote:
+1 for Type - it's the typical term used by Python developers, even though it's technically a misnomer from a type theory perspective. My intuition is that being consistent with the misnomer will be less confusing overall, based on the assumption that many more Pythonistas will be familiar with the type() builtin than will be familiar with formal type theory. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On Fri, May 13, 2016 at 6:29 PM, Guido van Rossum <guido@python.org> wrote:
I'm under the impression that you wanted someone to convince you about a potential mistake being made in naming it 'Type', so: I've heard people use "of" or other prepositions when reading aloud subcripts: Type[C] could be read as "type of C" or something similar. So, I could imagine someone forgetting or failing to realize that there is type(C) that already means "type of C", and that giving the runtime type of an object as a static type hint does not make a lot of sense. I'm not sure Class[C] really solves this problem either. Luckily I can just do SubType = typing.Type ;). To me that is clearer. I can't argue against 'longer', since 7 characters is almost double compared to 4. In addition, I would not mind if a plain `SubType` without [] would be a type hint equivalent to `type`, if that would then be desired for consistency. (not that you would have said you would mind that either). -- Koos

2016-05-12 20:49 GMT+02:00, Guido van Rossum <guido@python.org>:
It is very probably I dont understand. Is U here same type (=ProUser) for parameter and for return? Because in PEP484 is written: -> CT = TypeVar('CT', bound=Comparable) -> -> def min(x: CT, y: CT) -> CT: -> if x < y: -> return x -> else: -> return y -> -> min(1, 2) # ok, return type int -> min('x', 'y') # ok, return type str -> ->(Note that this is not ideal -- for example ``min('x', 1)`` is invalid ->at runtime but a type checker would simply infer the return type ->``Comparable``. Unfortunately, addressing this would require ->introducing a much more powerful and also much more complicated ->vconcept, F-bounded polymorphism. We may revisit this in the future.) which I understand that CT could be different type for x (=str) and y (=int).

On Thu, May 12, 2016 at 2:51 PM, Pavol Lisy <pavol.lisy@gmail.com> wrote:
Their status is different though. The return type is inferred from the argument type but (usually) not the other way around.
No, *in a given call* CT will be the same for all. The example with Comparable is that for `min('x', 1)` there is a solution where CT *is* actually the same for both arguments -- both 'x' and 1 are instances of Comparable. In that example this is not ideal. But in the new_user(UserPro) example there's only one occurrence of U in the argument list, and that will be inferred as the return type. An example more similar to the CT example with users would be something like def pair_accounts(personal_user_class: Type[U], business_use_class: Type[U]) -> List[U]: ... Here the return type of pair_accounts(ProUser, ProUser) would be inferred as List[ProUser], but the return type of pair_accounts(ProUser, TeamUser) would be inferred as List[User], since User is the common base class of ProUser and TeamUser. -- --Guido van Rossum (python.org/~guido)

Great. We'll be able to move this very quickly once we decide whether it should be called Type[C] or Class[C]. The original proposal is Type[C], and it's nice because it's a pun on `type`, just like Tuple[...] is a pun on `tuple` (and similar for List etc.). But OTOH it really annotates a class object, not an abstract type. I still prefer Type[C] -- anyone want to argue that we're making a mistake? (I know, nobody's listening, everyone's too busy arguing that Path should inherit from str. :-) -- --Guido van Rossum (python.org/~guido)

On Thu, May 12, 2016 at 8:00 PM, Chris Angelico <rosuav@gmail.com> wrote:
Well, actually, I call them class objects all the time... Plus PEP 484 (at least the recent revisions, though this has always been the BDFL-delegate's intention) tries to make a clear terminological between classes (the things you have at runtime) and types (the things that type checkers care about). There's a big overlap because most classes are also types -- but not the other way around! E.g. Any is a type but not a class (you can neither inherit from Any nor instantiate it), and the same is true for unions and type variables. And it's pretty clear that when you have an argument or variable annotated with Type[C] it has to be a real class object, not Any or a union or a type variable, because in the common use case, functions taking class objects either instantiate that class, call class methods on it, or use it for isinstance() checks, and none of those things would work if at run time they received Any or a union or type variable. Let's look at that function new_user() again: def new_user(user_class: Type[User]) -> User: return user_class() We can call new_user(ProUser) or new_user(TeamUser), but calling new_user(Any) isn't going to work at runtime, not us new_user(Union[ProUser, TeamUser]). Note also that this is a unique twist for Type[C] that doesn't exist for non-type annotations. If we have a function argument annotated with a class, e.g. def first_name(u: User) -> str: ... it's totally reasonable to call it with an argument whose type is Union[ProUser, TeamUser]: def get_user_by_id(id: str) -> Union[ProUser, TeamUser]: ... name = first_name(get_user_by_id('joe')) This is because here the *type* returned by get_user_by_id() is a union, but the actual *values* it returns are always instances of one of the runtime classes ProUser or TeamUser. If we had had more consistent terminology in Python from the start, this naming issue would never have come up -- we'd never have used type(x) as (almost) equivalent to x.__class__, and we wouldn't have used class C(type): ... to define a metaclass (i.e. a class's class). But that's water under the bridge, and we might a well write Type[C] rather than Class[C] to pub with `type`. And it seems only two people care about the difference -- Mark Shannon and myself. :-) So Type[C] it is. -- --Guido van Rossum (python.org/~guido)

On 05/12/2016 09:35 PM, Guido van Rossum wrote:
I definitely don't have enough CS background to understand the subtle distinction you're drawing here (at least, I hope it's subtle ;) . But to me, it's a type -- and when writing metaclasses, my decision for the name has always been between SomethingMeta or SomethingType, it's never been SomethingClass.
Cool. :) -- ~Ethan~

On 13 May 2016 at 12:52, Guido van Rossum <guido@python.org> wrote:
+1 for Type - it's the typical term used by Python developers, even though it's technically a misnomer from a type theory perspective. My intuition is that being consistent with the misnomer will be less confusing overall, based on the assumption that many more Pythonistas will be familiar with the type() builtin than will be familiar with formal type theory. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On Fri, May 13, 2016 at 6:29 PM, Guido van Rossum <guido@python.org> wrote:
I'm under the impression that you wanted someone to convince you about a potential mistake being made in naming it 'Type', so: I've heard people use "of" or other prepositions when reading aloud subcripts: Type[C] could be read as "type of C" or something similar. So, I could imagine someone forgetting or failing to realize that there is type(C) that already means "type of C", and that giving the runtime type of an object as a static type hint does not make a lot of sense. I'm not sure Class[C] really solves this problem either. Luckily I can just do SubType = typing.Type ;). To me that is clearer. I can't argue against 'longer', since 7 characters is almost double compared to 4. In addition, I would not mind if a plain `SubType` without [] would be a type hint equivalent to `type`, if that would then be desired for consistency. (not that you would have said you would mind that either). -- Koos

2016-05-12 20:49 GMT+02:00, Guido van Rossum <guido@python.org>:
It is very probably I dont understand. Is U here same type (=ProUser) for parameter and for return? Because in PEP484 is written: -> CT = TypeVar('CT', bound=Comparable) -> -> def min(x: CT, y: CT) -> CT: -> if x < y: -> return x -> else: -> return y -> -> min(1, 2) # ok, return type int -> min('x', 'y') # ok, return type str -> ->(Note that this is not ideal -- for example ``min('x', 1)`` is invalid ->at runtime but a type checker would simply infer the return type ->``Comparable``. Unfortunately, addressing this would require ->introducing a much more powerful and also much more complicated ->vconcept, F-bounded polymorphism. We may revisit this in the future.) which I understand that CT could be different type for x (=str) and y (=int).

On Thu, May 12, 2016 at 2:51 PM, Pavol Lisy <pavol.lisy@gmail.com> wrote:
Their status is different though. The return type is inferred from the argument type but (usually) not the other way around.
No, *in a given call* CT will be the same for all. The example with Comparable is that for `min('x', 1)` there is a solution where CT *is* actually the same for both arguments -- both 'x' and 1 are instances of Comparable. In that example this is not ideal. But in the new_user(UserPro) example there's only one occurrence of U in the argument list, and that will be inferred as the return type. An example more similar to the CT example with users would be something like def pair_accounts(personal_user_class: Type[U], business_use_class: Type[U]) -> List[U]: ... Here the return type of pair_accounts(ProUser, ProUser) would be inferred as List[ProUser], but the return type of pair_accounts(ProUser, TeamUser) would be inferred as List[User], since User is the common base class of ProUser and TeamUser. -- --Guido van Rossum (python.org/~guido)
participants (8)
-
Chris Angelico
-
Ethan Furman
-
Guido van Rossum
-
Koos Zevenhoven
-
Matthias Kramm
-
Nick Coghlan
-
Pavol Lisy
-
Robert Collins