10000 Various small documentation updates by JukkaL · Pull Request #11817 · python/mypy · GitHub
[go: up one dir, main page]

Skip to content

Various small documentation updates #11817

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Dec 22, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Mode updates
  • Loading branch information
JukkaL committed Dec 22, 2021
commit 51c16856b69b9baf4de6969074748078f93e674d
190 changes: 100 additions & 90 deletions docs/source/common_issues.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,102 +26,102 @@ No errors reported for obviously wrong code
There are several common reasons why obviously wrong code is not
flagged as an error.

- **The function containing the error is not annotated.** Functions that
do not have any annotations (neither for any argument nor for the
return type) are not type-checked, and even the most blatant type
errors (e.g. ``2 + 'a'``) pass silently. The solution is to add
annotations. Where that isn't possible, functions without annotations
can be checked using :option:`--check-untyped-defs <mypy --check-untyped-defs>`.
**The function containing the error is not annotated.** Functions that
do not have any annotations (neither for any argument nor for the
return type) are not type-checked, and even the most blatant type
errors (e.g. ``2 + 'a'``) pass silently. The solution is to add
annotations. Where that isn't possible, functions without annotations
can be checked using :option:`--check-untyped-defs <mypy --check-untyped-defs>`.

Example:
Example:

.. code-block:: python
.. code-block:: python

def foo(a):
return '(' + a.split() + ')' # No error!
def foo(a):
return '(' + a.split() + ')' # No error!

This gives no error even though ``a.split()`` is "obviously" a list
(the author probably meant ``a.strip()``). The error is reported
once you add annotations:
This gives no error even though ``a.split()`` is "obviously" a list
(the author probably meant ``a.strip()``). The error is reported
once you add annotations:

.. code-block:: python
.. code-block:: python

def foo(a: str) -> str:
return '(' + a.split() + ')'
# error: Unsupported operand types for + ("str" and List[str])
def foo(a: str) -> str:
return '(' + a.split() + ')'
# error: Unsupported operand types for + ("str" and List[str])

If you don't know what types to add, you can use ``Any``, but beware:
If you don't know what types to add, you can use ``Any``, but beware:

- **One of the values involved has type 'Any'.** Extending the above
example, if we were to leave out the annotation for ``a``, we'd get
no error:
**One of the values involved has type 'Any'.** Extending the above
example, if we were to leave out the annotation for ``a``, we'd get
no error:

.. code-block:: python
.. code-block:: python

def foo(a) -> str:
return '(' + a.split() + ')' # No error!
def foo(a) -> str:
return '(' + a.split() + ')' # No error!

The reason is that if the type of ``a`` is unknown, the type of
``a.split()`` is also unknown, so it is inferred as having type
``Any``, and it is no error to add a string to an ``Any``.
The reason is that if the type of ``a`` is unknown, the type of
``a.split()`` is also unknown, so it is inferred as having type
``Any``, and it is no error to add a string to an ``Any``.

If you're having trouble debugging such situations,
:ref:`reveal_type() <reveal-type>` might come in handy.
If you're having trouble debugging such situations,
:ref:`reveal_type() <reveal-type>` might come in handy.

Note that sometimes library stubs have imprecise type information,
e.g. the :py:func:`pow` builtin returns ``Any`` (see `typeshed issue 285
<https://github.com/python/typeshed/issues/285>`_ for the reason).
Note that sometimes library stubs have imprecise type information,
e.g. the :py:func:`pow` builtin returns ``Any`` (see `typeshed issue 285
<https://github.com/python/typeshed/issues/285>`_ for the reason).

- :py:meth:`__init__ <object.__init__>` **method has no annotated
arguments or return type annotation.** :py:meth:`__init__ <object.__init__>`
is considered fully-annotated **if at least one argument is annotated**,
while mypy will infer the return type as ``None``.
The implication is that, for a :py:meth:`__init__ <object.__init__>` method
that has no argument, you'll have to explicitly annotate the return type
as ``None`` to type-check this :py:meth:`__init__ <object.__init__>` method:
:py:meth:`__init__ <object.__init__>` **method has no annotated
arguments or return type annotation.** :py:meth:`__init__ <object.__init__>`
is considered fully-annotated **if at least one argument is annotated**,
while mypy will infer the return type as ``None``.
The implication is that, for a :py:meth:`__init__ <object.__init__>` method
that has no argument, you'll have to explicitly annotate the return type
as ``None`` to type-check this :py:meth:`__init__ <object.__init__>` method:

.. code-block:: python
.. code-block:: python

def foo(s: str) -> str:
return s

class A():
def __init__(self, value: str): # Return type inferred as None, considered as typed method
self.value = value
foo(1) # error: Argument 1 to "foo" has incompatible type "int"; expected "str"

class B():
def __init__(self): # No argument is annotated, considered as untyped method
foo(1) # No error!

class C():
def __init__(self) -> None: # Must specify return type to type-check
foo(1) # error: Argument 1 to "foo" has incompatible type "int"; expected "str"

- **Some imports may be silently ignored**. Another source of
unexpected ``Any`` values are the :option:`--ignore-missing-imports
<mypy --ignore-missing-imports>` and :option:`--follow-imports=skip
<mypy --follow-imports>` flags. When you use :option:`--ignore-missing-imports <mypy --ignore-missing-imports>`,
any imported module that cannot be found is silently replaced with
``Any``. When using :option:`--follow-imports=skip <mypy --follow-imports>` the same is true for
modules for which a ``.py`` file is found but that are not specified
on the command line. (If a ``.pyi`` stub is found it is always
processed normally, regardless of the value of
:option:`--follow-imports <mypy --follow-imports>`.) To help debug the former situation (no
module found at all) leave out :option:`--ignore-missing-imports <mypy --ignore-missing-imports>`; to get
clarity about the latter use :option:`--follow-imports=error <mypy --follow-imports>`. You can
read up about these and other useful flags in :ref:`command-line`.

- **A function annotated as returning a non-optional type returns 'None'
and mypy doesn't complain**.
def foo(s: str) -> str:
return s

class A():
def __init__(self, value: str): # Return type inferred as None, considered as typed method
self.value = value
foo(1) # error: Argument 1 to "foo" has incompatible type "int"; expected "str"

class B():
def __init__(self): # No argument is annotated, considered as untyped method
foo(1) # No error!

class C():
def __init__(self) -> None: # Must specify return type to type-check
foo(1) # error: Argument 1 to "foo" has incompatible type "int"; expected "str"

**Some imports may be silently ignored**. Another source of
unexpected ``Any`` values are the :option:`--ignore-missing-imports
<mypy --ignore-missing-imports>` and :option:`--follow-imports=skip
<mypy --follow-imports>` flags. When you use :option:`--ignore-missing-imports <mypy --ignore-missing-imports>`,
any imported module that cannot be found is silently replaced with
``Any``. When using :option:`--follow-imports=skip <mypy --follow-imports>` the same is true for
modules for which a ``.py`` file is found but that are not specified
on the command line. (If a ``.pyi`` stub is found it is always
processed normally, regardless of the value of
:option:`--follow-imports <mypy --follow-imports>`.) To help debug the former situation (no
module found at all) leave out :option:`--ignore-missing-imports <mypy --ignore-missing-imports>`; to get
clarity about the latter use :option:`--follow-imports=error <mypy --follow-imports>`. You can
read up about these and other useful flags in :ref:`command-line`.

**A function annotated as returning a non-optional type returns 'None'
and mypy doesn't complain**.

.. code-block:: python
.. code-block:: python

def foo() -> str:
return None # No error!
def foo() -> str:
return None # No error!

You may have disabled strict optional checking (see
:ref:`no_strict_optional` for more).
You may have disabled strict optional checking (see
:ref:`no_strict_optional` for more).

.. _silencing_checker:

Expand Down Expand Up @@ -634,32 +634,41 @@ You can install the latest development version of mypy from source. Clone the
Variables vs type aliases
-------------------------

Mypy has both type aliases and variables with types like ``Type[...]`` and it is important to know their difference.
Mypy has both *type aliases* and variables with types like ``Type[...]``. These are
subtly different, and it's important to understand how they differ to avoid pitfalls.

1. Variables with type ``Type[...]`` should be created by assignments with an explicit type annotations:
1. A variable with type ``Type[...]`` is defined using an assignment with an
explicit type annotation:

.. code-block:: python

class A: ...
tp: Type[A] = A

2. Aliases are created by assignments without an explicit type.
2. You can define a type alias using an assignment without an explicit type annotation
at the top level of a module:

.. code-block:: python

class A: ...
Alias = A

Or you can also use :pep:`613` and explicit type aliases:
You can also use ``TypeAlias`` (:pep:`613`) to define an *explicit type alias*:

.. code-block:: python
from typing import TypeAlias # or `from typing_extensions` before `python3.10`

from typing import TypeAlias # "from typing_extensions" in Python 3.9 and earlier

class A: ...
Alias: TypeAlias = A

3. The difference is that aliases are completely known statically and can be used in type context (annotations):
You should always use ``TypeAlias`` to define a type alias in a class body or
inside a function.

The main difference is that the target of an alias is precisely known statically, and this
means that they can be used in type annotations and other *type contexts*. Type aliases
can't be defined conditionally (unless using
:ref:`supported Python version and platform checks <version_and_platform_checks>`):

.. code-block:: python

Expand All @@ -669,17 +678,18 @@ Mypy has both type aliases and variables with types like ``Type[...]`` and it is
if random() > 0.5:
Alias = A
else:
Alias = B # error: Cannot assign multiple types to name "Alias" without an explicit "Type[...]" annotation \
# error: Incompatible types in assignment (expression has type "Type[B]", variable has type "Type[A]")
# error: Cannot assign multiple types to name "Alias" without an
# explicit "Type[...]" annotation
Alias = B

tp: Type[object] # tp is a type variable
tp: Type[object] # "tp" is a variable with a type object value
if random() > 0.5:
tp = A
else:
tp = B # This is OK

def fun1(x: Alias) -> None: ... # This is OK
def fun2(x: tp) -> None: ... # error: Variable "__main__.tp" is not valid as a type
def fun1(x: Alias) -> None: ... # OK
def fun2(x: tp) -> None: ... # Error: "tp" is not valid as a type

Incompatible overrides
----------------------
Expand Down
20 changes: 12 additions & 8 deletions docs/source/kinds_of_types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -521,21 +521,25 @@ assigning the type to a variable:
another type -- it's equivalent to the target type except for
:ref:`generic aliases <generic-type-aliases>`.

Since Mypy 0.930 you can also use explicit type aliases which are defined by :pep:`613`.
Since Mypy 0.930 you can also use *explicit type aliases*, which were
introduced in :pep:`613`.

Implicit type alias declaration rules create confusion when type aliases involve forward references,
invalid types, or violate other restrictions enforced on type alias declaration.
Because the distinction between an unannotated value and a type alias is implicit,
ambiguous or incorrect type alias declarations implicitly default to a valid value assignment.
There can be confusion about exactly when an assignment defines an implicit type alias --
for example, when the alias contains forward references, invalid types, or violates some other
restrictions on type alias declarations. Because the
distinction between an unannotated variable and a type alias is implicit,
ambiguous or incorrect type alias declarations default to defining
a normal variable instead of a type alias.

Explicit type aliases are unambiguous and can also improve readability by
making the intent clear:

.. code-block:: python

from typing import TypeAlias # or `from typing_extensions` before `python3.10`
from typing import TypeAlias # "from typing_extensions" in Python 3.9 and earlier

AliasType: TypeAlias = Union[list[dict[tuple[int, str], set[int]]], tuple[str, list[str]]]

Explicit type aliases are unambiguous and improve readability.

.. _named-tuples:

Named tuples
Expand Down
0