blob: 424471f08752f8be5e08b2afcf326efbd54d65c1 [file] [log] [blame]
Generics
--------
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