Types
Let's look at what fits into a type hint, and how we might describe values more complex than 42
.
Data Types
Before runtime, Python classes are just types, so we can use them in our type hints:
from datetime import time
z: None = None
a: int = 42
b: float = 4.2
c: str = "42"
d: bytes = b"42"
e: time = time(0, 0, 0, 0)
Unions
We can create a new type by combining existing types with a union. Subtypes of the union are subtypes of at least one of the union's members.
a: str | int = 42
a = "hello"
Our variable can have a value of type str
or int
because both are subtypes of the union type str | int
.1
You may also see typing.Optional[x]
which is equivalent to a union with None
: x | None
.
Union
's theoretical sibling Intersection
, whose subtypes are subtypes of all the intersection's members, does not yet exist in Python.2
Collections
Collections can also be typed. Collection types are generic types and accept type arguments which can be used to specify the type of their members: 3
f: list[str | int] = ["hello", "world", 4, 2]
g: set[int] = {1, 2, 3}
# The number of arguments is equal to the size of the tuple
h: tuple[int, str, bool] = (0, "xyz", True)
# The first argument describes the keys, the second the values
i: dict[str, str] = {"key": "value"}
Classes
Our own classes can be used as types just like the above. Their subclasses are also their subtypes.
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
class Point3D(Point):
def __init__(self, x, y, z):
super().__init__(x, y)
self.z = z
origin: Point = Point(0, 0)
origin = Point3D(0, 0, 0)
Any
There is also a special Any
type. All values are assignable to Any
:
a: Any
a = 42
a = 4.2
a = "42"
And Any
is compatible with every type:
a: Any = "hello"
b: int = a
c: float = a
Untyped values become Any
when their types cannot be inferred. You will most commonly see Any
originate in untyped code or libraries. Pyright aliases Any
as Unknown
when types cannot be determined from imports or library code.4
Any
can function as an escape hatch, at the expense of type safety and language tools. It's best to avoid it if possible. When you encounter an Any
value, it's worthwhile to see if you can type it.
Literals
The types above all specify the type of a value, but Literal
types go further — they specify specific values.5
For example, the variable below has only 5 possible string values:
from typing import Literal
method: Literal["GET", "PUT", "POST", "PATCH", "DELETE"]
method = "GET"
method = "GOT"
#> Expression of type "Literal['GOT']" cannot be assigned to declared type "Literal['GET', 'PUT', 'POST', 'PATCH', 'DELETE']"
Passing multiple values like Literal[1, 2]
is a shorthand for a union: Literal[1] | Literal[2]
.
Literal
can be used for any value you can write 'literally,' i.e. without a constructor:
a: Literal[1, 2, 3]
b: Literal["arthur", "zaphod"]
c: Literal[b"1001"]
d: Literal[True]
But the values must be written literally:
a: Literal[42] = int(42)
#> Expression of type "int" cannot be assigned to declared type "Literal[42]"
Inference
You will see slightly different inferred types for some of the above code if you test it. For example:
a: int = 42
b: str = "hello"
The type checker attempts to infer the most specific subtype for the value. For a
that is not int
, it is Literal[42]
. For b
, it is Literal["hello"]
.
Footnotes
-
Prior to Python 3.10, unions are written with the Union type from the typing module:
Union[str, None]
↩ -
An intersection specification is being drafted for a future PEP ↩
-
Collection types were imported from the
typing
module prior to Python 3.9, e.g.from typing import List
. ↩ -
https://microsoft.github.io/pyright/#/type-inference?id=unknown-type ↩
-
https://docs.python.org/3.10/library/typing.html#typing.Literal https://adamj.eu/tech/2021/07/09/python-type-hints-how-to-use-typing-literal/ ↩