The subscript operation on container types is overloaded to support specifying the item type for the container as part of a type hint. Example:
from typing import List def space_join(a: List[str]) -> str: """Join list items with spaces.""" return ' '.join(a)
Given this definition, the following code will be flagged as a type
error, because the argument has type List[int]
(list of integers)
rather than List[str]
(list of strings):
space_join([1, 2, 3]) # Error
Depending on the definition of the container, multiple type arguments may be given in this notation:
from collections import defaultdict from typing import Iterable, Dict def word_count(words: Iterable[str]) -> Dict[str, int]: """Count words in document.""" res = defaultdict(int) for word in words: res[word] += 1 return dict(res) print(word_count(['to', 'be', 'or', 'not', 'to', 'be'])) # {'to': 2, 'be': 2, 'or': 1, 'not': 1}
Sometimes we want the type of several arguments and/or the return type
to vary collectively. We can do this using type variables. A type
variable must be defined using the TypeVar()
factory, after which
it can be used in mutiple function or method signatures. This is
called a generic function. Example:
from collections import defaultdict from typing import Iterable, Mapping, TypeVar T = TypeVar('T') def thing_count(things: Iterable[T]) -> Dict[T, int]: """Count words in document.""" res = defaultdict(int) for thing in things: res[thing] += 1 return dict(res) print(thing_count([2, 3, 5, 7, 2, 3]) # {2: 2, 3: 2, 5: 1, 7: 1}
Note that the argument to TypeVar()
must be a string, and it must
be assigned to a variable with exactly that name. Type variables
cannot be redefined in the same module. These constraints are
enforced by the type checker (but not by the runtime implementation of
TypeVar()
).
We can also define generic classes, as follows:
from typing import Generic, TypeVar T = TypeVar('T') class Node(Generic[T]): def __init__(self, label: T) -> None: self.label = label def get_label(self) -> T: return self.label def mknod(x: int) -> Node[int]: return Node(x) print(mknod(40).get_label() + 2) # 42
This same mechanism is used to define the container classes exported
by typing
(although the implementation is hairier due to the
desire to also emulate collection ABCs).
Type variables have a few more tricks up their sleeves:
Additional positional arguments must be type expressions that will be used to constrain the types that are acceptable substitutions. This feature is used for example by the predefined type variable
AnyStr
, which is defined as:AnyStr = TypeVar('AnyStr', str, bytes)
When such a constrained type variable is used in an argument type, the actual type must be a subtype of one of the constraints. When used in a return value type, the inferred return type will be exactly the corresponding constraint (not the inferred argument type, which may be a subtype thereof). For example:
from typing import AnyStr def add_strings(a: AnyStr, b: AnyStr) -> AnyStr: return a+b add_string('x', 'y') # 'xy' add_string(b'a', b'b') # b'ab' add_string('x', b'z') # Error class MyStr(str): pass add_string(MyStr('a'), MyStr('b')) # 'ab', not MyStr('ab')
Type variables may be declared as covariant or contravariant. The default is invariant. Covariance is best explained using an example:
from typing import TypeVar T = TypeVar('T') Tco = TypeVar('Tco', covariant=True) class MyTuple(Generic[Tco]): ... # Implements immutable sequence operations class MyList(MyTuple[T]): ... # Adds mutable sequence operations class Employee: ... class Manager(Employee): ... issubclass(MyTuple[Manager], MyTuple[Employee]) # True issubclass(MyList[Manager], MyList[Employee]) # False def print_employees(emps: MyTuple[Employee]) -> None: for emp in emps: print(emp) def add_employee(emps: MyList[Employee], emp: Employee) -> None: emps.append(emp) mgrs = MyList[Manager](...) # Undecided if this is allowed print_employees(mgrs) # OK bob = Manager(...) add_employee(mgrs, bob) # Error
For a good if theoretical explanation of covariance and contravariance see the Wikipedia article: http://en.wikipedia.org/wiki/Covariance_and_contravariance_%28computer_science%29