[WIP] Semantics of variance in type variables and notation conventions (#68)

* Semantics of variance in type variables and notation conventions

* Notation convention for type variables in PEP 8
This commit is contained in:
Ivan Levkivskyi 2016-08-03 20:33:00 +02:00 committed by Guido van Rossum
parent afebe82ef4
commit a919235364
2 changed files with 53 additions and 27 deletions

View File

@ -829,6 +829,19 @@ Note that there is a separate convention for builtin names: most builtin
names are single words (or two words run together), with the CapWords
convention used only for exception names and builtin constants.
Type variable names
~~~~~~~~~~~~~~~~~~~
Names of type variables introduced in PEP 484 should normally use CapWords
preferring short names: ``T``, ``AnyStr``, ``Num``. It is recommended to add
suffixes ``_co`` or ``_contra`` to the variables used to declare covariant
or contravariant behavior correspondingly. Examples::
from typing import TypeVar
VT_co = TypeVar('VT_co', covariant=True)
KT_contra = TypeVar('KT_contra', contravariant=True)
Exception Names
~~~~~~~~~~~~~~~

View File

@ -724,31 +724,34 @@ It turns out such an argument acts *contravariantly*, whereas the
intuitive answer (which is correct in case the function doesn't mutate
its argument!) requires the argument to act *covariantly*. A longer
introduction to these concepts can be found on Wikipedia
[wiki-variance]_; here we just show how to control a type checker's
behavior.
[wiki-variance]_ and in PEP 483; here we just show how to control
a type checker's behavior.
By default type variables are considered *invariant*, which means that
arguments for arguments annotated with types like ``List[Employee]``
must exactly match the type annotation -- no subclasses or
By default generic types are considered *invariant* in all type variables,
which means that values for variables annotated with types like
``List[Employee]`` must exactly match the type annotation -- no subclasses or
superclasses of the type parameter (in this example ``Employee``) are
allowed.
To facilitate the declaration of container types where covariant type
checking is acceptable, a type variable can be declared using
``covariant=True``. For the (rare) case where contravariant behavior
is desirable, pass ``contravariant=True``. At most one of these may
be passed.
To facilitate the declaration of container types where covariant or
contravariant type checking is acceptable, type variables accept keyword
arguments ``covariant=True`` or ``contravariant=True``. At most one of these
may be passed. Generic types defined with such variables are considered
covariant or contravariant in the corresponding variable. By convention,
it is recommended to use names ending in ``_co`` for type variables
defined with ``covariant=True`` and names ending in ``_contra`` for that
defined with ``contravariant=True``.
A typical example involves defining an immutable (or read-only)
container class::
from typing import TypeVar, Generic, Iterable, Iterator
T = TypeVar('T', covariant=True)
T_co = TypeVar('T_co', covariant=True)
class ImmutableList(Generic[T]):
def __init__(self, items: Iterable[T]) -> None: ...
def __iter__(self) -> Iterator[T]: ...
class ImmutableList(Generic[T_co]):
def __init__(self, items: Iterable[T_co]) -> None: ...
def __iter__(self) -> Iterator[T_co]: ...
...
class Employee: ...
@ -762,16 +765,19 @@ container class::
mgrs = ImmutableList([Manager()]) # type: ImmutableList[Manager]
dump_employees(mgrs) # OK
The read-only collection classes in ``typing`` are all defined using a
covariant type variable (e.g. ``Mapping`` and ``Sequence``). The
The read-only collection classes in ``typing`` are all declared
covariant in their type variable (e.g. ``Mapping`` and ``Sequence``). The
mutable collection classes (e.g. ``MutableMapping`` and
``MutableSequence``) are defined using regular invariant type
variables. The one example of a contravariant type variable is the
``Generator`` type, which is contravariant in the ``send()`` argument
type (see below).
``MutableSequence``) are declared invariant. The one example of
a contravariant type is the ``Generator`` type, which is contravariant
in the ``send()`` argument type (see below).
Note: variance affects type parameters for generic types -- it does
not affect regular parameters. For example, the following example is
Note: Covariance or contravariance is *not* a property of a type variable,
but a property of a generic class defined using this variable.
Variance is only applicable to generic types; generic functions
do not have this property. The latter should be defined using only
type variables without ``covariant`` or ``contravariant`` keyword arguments.
For example, the following example is
fine::
from typing import TypeVar
@ -780,12 +786,19 @@ fine::
class Manager(Employee): ...
E = TypeVar('E', bound=Employee) # Invariant
E = TypeVar('E', bound=Employee)
def dump_employee(e: E) -> None: ...
dump_employee(Manager()) # OK
while the following is prohibited::
B_co = TypeVar('B_co', covariant=True)
def bad_func(x: B_co) -> B_co: # Flagged as error by a type checker
...
The numeric tower
-----------------
@ -1728,10 +1741,10 @@ Generic variants of container ABCs (and a few non-containers):
* Generator, used as ``Generator[yield_type, send_type,
return_type]``. This represents the return value of generator
functions. It is a subtype of ``Iterable`` and it has additional
type variables for the type accepted by the ``send()`` method (which
is contravariant -- a generator that accepts sending it ``Employee``
instance is valid in a context where a generator is required that
accepts sending it ``Manager`` instances) and the return type of the
type variables for the type accepted by the ``send()`` method (it
is contravariant in this variable -- a generator that accepts sending it
``Employee`` instance is valid in a context where a generator is required
that accepts sending it ``Manager`` instances) and the return type of the
generator.
* Hashable (not generic, but present for completeness)