diff --git a/docs/source/class_basics.rst b/docs/source/class_basics.rst index e03c299e977c..3dc116cfb089 100644 --- a/docs/source/class_basics.rst +++ b/docs/source/class_basics.rst @@ -158,30 +158,32 @@ Protocols and structural subtyping .. note:: - The support for structural subtyping is still experimental. Some features - might be not yet implemented, mypy could pass unsafe code or reject - working code. - -There are two main type systems with respect to subtyping: nominal subtyping -and structural subtyping. The *nominal* subtyping is based on class hierarchy, -so that if class ``D`` inherits from class ``C``, then it is a subtype -of ``C``. This type system is primarily used in mypy since it allows -to produce clear and concise error messages, and since Python provides native -``isinstance()`` checks based on class hierarchy. The *structural* subtyping -however has its own advantages. In this system class ``D`` is a subtype -of class ``C`` if the former has all attributes of the latter with -compatible types. - -This type system is a static equivalent of duck typing, well known by Python -programmers. Mypy provides an opt-in support for structural subtyping via -protocol classes described in this section. -See `PEP 544 `_ for -specification of protocols and structural subtyping in Python. - -User defined protocols -********************** - -To define a protocol class, one must inherit the special + Structural subtyping is experimental. Some things may not + work as expected. Mypy may pass unsafe code or it can reject + valid code. + +Mypy supports two ways of deciding whether two classes are compatible +as types: nominal subtyping and structural subtyping. *Nominal* +subtyping is strictly based on the class hierarchy. If class ``D`` +inherits class ``C``, it's also a subtype of ``C``. This form of +subtyping is used by default in mypy, since it's easy to understand +and produces clear and concise error messages, and since it matches +how the native ``isinstance()`` check works -- based on class +hierarchy. *Structural* subtyping can also be useful. Class ``D`` is +a structural subtype of class ``C`` if the former has all attributes +and methods of the latter, and with compatible types. + +Structural subtyping can be seen as a static equivalent of duck +typing, which is well known to Python programmers. Mypy provides an +opt-in support for structural subtyping via protocol classes described +below. See `PEP 544 `_ for +the detailed specification of protocols and structural subtyping in +Python. + +Simple user-defined protocols +***************************** + +You can define a protocol class by inheriting the special ``typing_extensions.Protocol`` class: .. code-block:: python @@ -191,37 +193,42 @@ To define a protocol class, one must inherit the special class SupportsClose(Protocol): def close(self) -> None: - ... + ... # Explicit '...' + + class Resource: # No SupportsClose base class! + # ... some methods ... - class Resource: # Note, this class does not have 'SupportsClose' base. - # some methods def close(self) -> None: self.resource.release() - def close_all(things: Iterable[SupportsClose]) -> None: - for thing in things: - thing.close() + def close_all(items: Iterable[SupportsClose]) -> None: + for item in items: + item.close() - close_all([Resource(), open('some/file')]) # This passes type check + close_all([Resource(), open('some/file')]) # Okay! + +``Resource`` is a subtype of the ``SupportClose`` protocol since it defines +a compatible ``close`` method. Regular file objects returned by ``open()`` are +similarly compatible with the protocol, as they support ``close()``. .. note:: - The ``Protocol`` base class is currently provided in ``typing_extensions`` - package. When structural subtyping is mature and - `PEP 544 `_ is accepted, - ``Protocol`` will be included in the ``typing`` module. As well, several - types such as ``typing.Sized``, ``typing.Iterable`` etc. will be made - protocols. + The ``Protocol`` base class is currently provided in the ``typing_extensions`` + package. Once structural subtyping is mature and + `PEP 544 `_ has been accepted, + ``Protocol`` will be included in the ``typing`` module. Several library + types such as ``typing.Sized`` and ``typing.Iterable`` will also be changed + into protocols. They are currently treated as regular ABCs by mypy. Defining subprotocols ********************* -Subprotocols are also supported. Existing protocols can be extended -and merged using multiple inheritance. For example: +You can also define subprotocols. Existing protocols can be extended +and merged using multiple inheritance. Example: .. code-block:: python - # continuing from previous example + # ... continuing from the previous example class SupportsRead(Protocol): def read(self, amount: int) -> bytes: ... @@ -232,20 +239,19 @@ and merged using multiple inheritance. For example: class AdvancedResource(Resource): def __init__(self, label: str) -> None: self.label = label + def read(self, amount: int) -> bytes: # some implementation ... - resource = None # type: TaggedReadableResource - - # some code - + resource: TaggedReadableResource resource = AdvancedResource('handle with care') # OK -Note that inheriting from existing protocols does not automatically turn -a subclass into a protocol, it just creates a usual (non-protocol) ABC that -implements given protocols. The ``typing_extensions.Protocol`` base must always -be explicitly present: +Note that inheriting from an existing protocol does not automatically +turn the subclass into a protocol -- it just creates a regular +(non-protocol) ABC that implements the given protocol (or +protocols). The ``typing_extensions.Protocol`` base class must always +be explicitly present if you are defining a protocol: .. code-block:: python @@ -253,25 +259,27 @@ be explicitly present: new_attr: int class Concrete: - new_attr = None # type: int + new_attr: int = 0 + def close(self) -> None: ... - # Below is an error, since nominal subtyping is used by default - x = Concrete() # type: NewProtocol # Error! + + # Error: nominal subtyping used by default + x: NewProtocol = Concrete() # Error! .. note:: - The `PEP 526 `_ variable - annotations can be used to declare protocol attributes. However, protocols - are also supported on Python 2.7 and Python 3.3+ with the help of type - comments and properties, see - `backwards compatibility in PEP 544 `_. + You can use Python 3.6 variable annotations (`PEP 526 + `_) + to declare protocol attributes. On Python 2.7 and earlier Python 3 + versions you can use type comments and properties. Recursive protocols ******************* -Protocols can be recursive and mutually recursive. This could be useful for -declaring abstract recursive collections such as trees and linked lists: +Protocols can be recursive (self-referential) and mutually +recursive. This is useful for declaring abstract recursive collections +such as trees and linked lists: .. code-block:: python @@ -280,8 +288,10 @@ declaring abstract recursive collections such as trees and linked lists: class TreeLike(Protocol): value: int + @property def left(self) -> Optional['TreeLike']: ... + @property def right(self) -> Optional['TreeLike']: ... @@ -296,9 +306,9 @@ declaring abstract recursive collections such as trees and linked lists: Using ``isinstance()`` with protocols ************************************* -To use a protocol class with ``isinstance()``, one needs to decorate it with -a special ``typing_extensions.runtime`` decorator. It will add support for -basic runtime structural checks: +You can use a protocol class with ``isinstance()`` if you decorate it +with the ``typing_extensions.runtime`` class decorator. The decorator +adds support for basic runtime structural checks: .. code-block:: python @@ -314,11 +324,9 @@ basic runtime structural checks: mug = Mug() if isinstance(mug, Portable): - use(mug.handles) # Works statically and at runtime. + use(mug.handles) # Works statically and at runtime .. note:: - ``isinstance()`` is with protocols not completely safe at runtime. + ``isinstance()`` with protocols is not completely safe at runtime. For example, signatures of methods are not checked. The runtime - implementation only checks the presence of all protocol members - in object's MRO. - + implementation only checks that all protocol members are defined. diff --git a/docs/source/generics.rst b/docs/source/generics.rst index 8f8f5355b93e..2ea9b424e139 100644 --- a/docs/source/generics.rst +++ b/docs/source/generics.rst @@ -328,9 +328,9 @@ Let us illustrate this by few simple examples: def salaries(staff: List[Manager], accountant: Callable[[Manager], int]) -> List[int]: ... - this function needs a callable that can calculate a salary for managers, and + This function needs a callable that can calculate a salary for managers, and if we give it a callable that can calculate a salary for an arbitrary - employee, then it is still safe. + employee, it's still safe. * ``List`` is an invariant generic type. Naively, one would think that it is covariant, but let us consider this code: @@ -338,6 +338,7 @@ Let us illustrate this by few simple examples: class Shape: pass + class Circle(Shape): def rotate(self): ... @@ -349,7 +350,7 @@ Let us illustrate this by few simple examples: add_one(my_things) # This may appear safe, but... my_things[0].rotate() # ...this will fail - Another example of invariant type is ``Dict``, most mutable containers + Another example of invariant type is ``Dict``. Most mutable containers are invariant. By default, mypy assumes that all user-defined generics are invariant. @@ -360,15 +361,18 @@ type variables defined with special keyword arguments ``covariant`` or .. code-block:: python from typing import Generic, TypeVar + T_co = TypeVar('T_co', covariant=True) class Box(Generic[T_co]): # this type is declared covariant def __init__(self, content: T_co) -> None: self._content = content + def get_content(self) -> T_co: return self._content def look_into(box: Box[Animal]): ... + my_box = Box(Cat()) look_into(my_box) # OK, but mypy would complain here for an invariant type @@ -491,73 +495,6 @@ restrict the valid values for the type parameter in the same way. A type variable may not have both a value restriction (see :ref:`type-variable-value-restriction`) and an upper bound. -Generic protocols -***************** - -Generic protocols (see :ref:`protocol-types`) are also supported, generic -protocols mostly follow the normal rules for generic classes, the main -difference is that mypy checks that declared variance of type variables is -compatible with the class definition. Examples: - -.. code-block:: python - - from typing import TypeVar - from typing_extensions import Protocol - - T = TypeVar('T') - - class Box(Protocol[T]): - content: T - - def do_stuff(one: Box[str], other: Box[bytes]) -> None: - ... - - class StringWrapper: - def __init__(self, content: str) -> None: - self.content = content - - class BytesWrapper: - def __init__(self, content: bytes) -> None: - self.content = content - - do_stuff(StringWrapper('one'), BytesWrapper(b'other')) # OK - - x = None # type: Box[float] - y = None # type: Box[int] - x = y # Error, since the protocol 'Box' is invariant. - - class AnotherBox(Protocol[T]): # Error, covariant type variable expected - def content(self) -> T: - ... - - T_co = TypeVar('T_co', covariant=True) - class AnotherBox(Protocol[T_co]): # OK - def content(self) -> T_co: - ... - - ax = None # type: AnotherBox[float] - ay = None # type: AnotherBox[int] - ax = ay # OK for covariant protocols - -See :ref:`variance-of-generics` above for more details on variance. -Generic protocols can be recursive, for example: - -.. code-block:: python - - T = TypeVar('T') - class Linked(Protocol[T]): - val: T - def next(self) -> 'Linked[T]': ... - - class L: - val: int - def next(self) -> 'L': ... - - def last(seq: Linked[T]) -> T: - ... - - result = last(L()) # The inferred type of 'result' is 'int' - .. _declaring-decorators: Declaring decorators @@ -608,3 +545,94 @@ Also note that the ``wrapper()`` function is not type-checked. Wrapper functions are typically small enough that this is not a big problem. This is also the reason for the ``cast()`` call in the ``return`` statement in ``my_decorator()``. See :ref:`casts`. + +Generic protocols +***************** + +Mypy supports generic protocols (see also :ref:`protocol-types`). Generic +protocols mostly follow the normal rules for generic classes. Example: + +.. code-block:: python + + from typing import TypeVar + from typing_extensions import Protocol + + T = TypeVar('T') + + class Box(Protocol[T]): + content: T + + def do_stuff(one: Box[str], other: Box[bytes]) -> None: + ... + + class StringWrapper: + def __init__(self, content: str) -> None: + self.content = content + + class BytesWrapper: + def __init__(self, content: bytes) -> None: + self.content = content + + do_stuff(StringWrapper('one'), BytesWrapper(b'other')) # OK + + x: Box[float] = ... + y: Box[int] = ... + x = y # Error -- Box is invariant + +The main difference between generic protocols and ordinary generic +classes is that mypy checks that the declared variances of generic +type variables in a protocol match how they are used in the protocol +definition. The protocol in this example is rejected, since the type +variable ``T`` is used covariantly as a return type, but the type +variable is invariant: + +.. code-block:: python + + from typing import TypeVar + from typing_extensions import Protocol + + T = TypeVar('T') + + class ReadOnlyBox(Protocol[T]): # Error: covariant type variable expected + def content(self) -> T: ... + +This example correctly uses a covariant type variable: + +.. code-block:: python + + from typing import TypeVar + from typing_extensions import Protocol + + T_co = TypeVar('T_co', covariant=True) + + class ReadOnlyBox(Protocol[T_co]): # OK + def content(self) -> T_co: ... + + ax: ReadOnlyBox[float] = ... + ay: ReadOnlyBox[int] = ... + ax = ay # OK -- ReadOnlyBox is covariant + +See :ref:`variance-of-generics` for more about variance. + +Generic protocols can also be recursive. Example: + +.. code-block:: python + + T = TypeVar('T') + + class Linked(Protocol[T]): + val: T + def next(self) -> 'Linked[T]': ... + + class L: + val: int + + ... # details omitted + + def next(self) -> 'L': + ... # details omitted + + def last(seq: Linked[T]) -> T: + ... # implementation omitted + + result = last(L()) # Inferred type of 'result' is 'int' diff --git a/docs/source/type_inference_and_annotations.rst b/docs/source/type_inference_and_annotations.rst index 76e9cf9d9014..2ae6396c2d5e 100644 --- a/docs/source/type_inference_and_annotations.rst +++ b/docs/source/type_inference_and_annotations.rst @@ -78,6 +78,22 @@ type: x = 1.1 # type: Union[int, str] # Error! +Python 3.6 introduced a new syntax for variable annotations, which +resembles function annotations: + +.. code-block:: python + + x: Union[int, str] = 1 + +We'll use both syntax variants in examples. The syntax variants are +mostly interchangeable, but the Python 3.6 syntax allows defining the +type of a variable without initialization, which is not possible with +the comment-based syntax: + +.. code-block:: python + + x: str # Declare type of 'x' without initialization + .. note:: The best way to think about this is that the type comment sets the