Today, I came across a function type hinted with type
.
I have done some research as to when one should type hint with type
or Type
, and I can't find a satisfactory answer. From my research it seems there's some overlap between the two.
My question:
- What is the difference between
type
andType
? - What is an example use case that shows when to use
type
vsType
?
Research
Looking at the source for Type
(from typing
tag 3.7.4.3
), I can see this:
# Internal type variable used for Type[]. CT_co = TypeVar('CT_co', covariant=True, bound=type) # This is not a real generic class. Don't use outside annotations. class Type(Generic[CT_co], extra=type): """A special construct usable to annotate class objects. ```
It looks like Type
may just be an alias for type
, except it supports Generic
parameterization. Is this correct?
Example
Here is some sample code made using Python==3.8.5
and mypy==0.782
:
from typing import Type
def foo(val: type) -> None:
reveal_type(val) # mypy output: Revealed type is 'builtins.type'
def bar(val: Type) -> None:
reveal_type(val) # mypy output: Revealed type is 'Type[Any]'
class Baz:
pass
foo(type(bool))
foo(Baz)
foo(Baz()) # error: Argument 1 to "foo" has incompatible type "Baz"; expected "type"
bar(type(bool))
bar(Baz)
bar(Baz()) # error: Argument 1 to "bar" has incompatible type "Baz"; expected "Type[Any]"
Clearly mypy
recognizes a difference.
type
is a metaclass. Just like object instances are instances of classes, classes are instances of metaclasses.Type
is an annotation used to tell a type checker that a class object itself is to be handled at wherever the annotation is used, instead of an instance of that class object.There's a couple ways they are related.
type
is applied to an argument isType
. This is in the same way thatlist
applied to an argument (likelist((1, 2))
) has an annotated returned type ofList
. Using reveal_type in:we are asking what is the inferred type annotation for the return value of
type
when it is given 1. The answer isType
, more specificallyType[Literal[1]]
.Type
a type-check-time construct,type
is a runtime construct. This has various implications I'll explain later.Moving onto your examples, in:
We are not annotating
extra
astype
, we are instead passing the keyword-argumentextra
with valuetype
to the metaclass ofType
. See Class-level Keyword Arguments for more examples of this construct. Note thatextra=type
is very different fromextra: type
: one is assigning a value at runtime, and one is annotating with a type hint at type-check time.Now for the interesting part: if
mypy
is able to do successful type checking with both, why use one over the other? The answer lies in thatType
, being a type-check time construct, is much more well integrated with the typing ecosystem. Given this example:v1
,v3
andv4
type-check successfully. You can see thatv4
fromnaive
was a false positive, given that the type of1
isint
, notstr
. But because you cannot parametrized thetype
metaclass (it is notGeneric
), we're unable to get the safety that we have withsmart
.I consider this to be more of a language limitation. You can see PEP 585 which is attempting to bridge the same kind of gap, but for
list
/List
. At the end of the day though, the idea is still the same: the lowercase version is the runtime class, the uppercase version is the type annotation. Both can overlap, but there are features exclusive to both.