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
typeandType? - What is an example use case that shows when to use
typevsType?
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.
typeis a metaclass. Just like object instances are instances of classes, classes are instances of metaclasses.Typeis 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.
typeis applied to an argument isType. This is in the same way thatlistapplied 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
typewhen it is given 1. The answer isType, more specificallyType[Literal[1]].Typea type-check-time construct,typeis a runtime construct. This has various implications I'll explain later.Moving onto your examples, in:
We are not annotating
extraastype, we are instead passing the keyword-argumentextrawith valuetypeto the metaclass ofType. See Class-level Keyword Arguments for more examples of this construct. Note thatextra=typeis 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
mypyis 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,v3andv4type-check successfully. You can see thatv4fromnaivewas a false positive, given that the type of1isint, notstr. But because you cannot parametrized thetypemetaclass (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.