diff --git a/docs/source/dynamic_typing.rst b/docs/source/dynamic_typing.rst index 390bc52d9e2c..d3476de2ca64 100644 --- a/docs/source/dynamic_typing.rst +++ b/docs/source/dynamic_typing.rst @@ -4,27 +4,39 @@ Dynamically typed code ====================== -As mentioned earlier, bodies of functions that don't have any explicit -types in their function annotation are dynamically typed (operations -are checked at runtime). Code outside functions is statically typed by -default, and types of variables are inferred. This does usually the -right thing, but you can also make any variable dynamically typed by -defining it explicitly with the type ``Any``: +In :ref:`getting-started-dynamic-vs-static`, we discussed how bodies of functions +that don't have any explicit type annotations in their function are "dynamically typed" +and that mypy will not check them. In this section, we'll talk a little bit more +about what that means and how you can enable dynamic typing on a more fine grained basis. + +In cases where your code is too magical for mypy to understand, you can make a +variable or parameter dynamically typed by explicitly giving it the type +``Any``. Mypy will let you do basically anything with a value of type ``Any``, +including assigning a value of type ``Any`` to a variable of any type (or vice +versa). .. code-block:: python from typing import Any - s = 1 # Statically typed (type int) - d: Any = 1 # Dynamically typed (type Any) - s = 'x' # Type check error - d = 'x' # OK + num = 1 # Statically typed (inferred to be int) + num = 'x' # error: Incompatible types in assignment (expression has type "str", variable has type "int") + + dyn: Any = 1 # Dynamically typed (type Any) + dyn = 'x' # OK + + num = dyn # No error, mypy will let you assign a value of type Any to any variable + num += 1 # Oops, mypy still thinks num is an int + +You can think of ``Any`` as a way to locally disable type checking. +See :ref:`silencing-type-errors` for other ways you can shut up +the type checker. Operations on Any values ------------------------ -You can do anything using a value with type ``Any``, and type checker -does not complain: +You can do anything using a value with type ``Any``, and the type checker +will not complain: .. code-block:: python @@ -37,7 +49,7 @@ does not complain: open(x).read() return x -Values derived from an ``Any`` value also often have the type ``Any`` +Values derived from an ``Any`` value also usually have the type ``Any`` implicitly, as mypy can't infer a more precise result type. For example, if you get the attribute of an ``Any`` value or call a ``Any`` value the result is ``Any``: @@ -45,12 +57,45 @@ example, if you get the attribute of an ``Any`` value or call a .. code-block:: python def f(x: Any) -> None: - y = x.foo() # y has type Any - y.bar() # Okay as well! + y = x.foo() + reveal_type(y) # Revealed type is "Any" + z = y.bar("mypy will let you do anything to y") + reveal_type(z) # Revealed type is "Any" ``Any`` types may propagate through your program, making type checking less effective, unless you are careful. +Function parameters without annotations are also implicitly ``Any``: + +.. code-block:: python + + def f(x) -> None: + reveal_type(x) # Revealed type is "Any" + x.can.do["anything", x]("wants", 2) + +You can make mypy warn you about untyped function parameters using the +:option:`--disallow-untyped-defs ` flag. + +Generic types missing type parameters will have those parameters implicitly +treated as ``Any``: + +.. code-block:: python + + from typing import List + + def f(x: List) -> None: + reveal_type(x) # Revealed type is "builtins.list[Any]" + reveal_type(x[0]) # Revealed type is "Any" + x[0].anything_goes() # OK + +You can make mypy warn you about untyped function parameters using the +:option:`--disallow-any-generics ` flag. + +Finally, another major source of ``Any`` types leaking into your program is from +third party libraries that mypy does not know about. This is particularly the case +when using the :option:`--ignore-missing-imports ` +flag. See :ref:`fix-missing-imports` for more information about this. + Any vs. object -------------- @@ -80,6 +125,11 @@ operations: n: int = 1 n = o # Error! + +If you're not sure whether you need to use :py:class:`object` or ``Any``, use +:py:class:`object` -- only switch to using ``Any`` if you get a type checker +complaint. + You can use different :ref:`type narrowing ` techniques to narrow :py:class:`object` to a more specific type (subtype) such as ``int``. Type narrowing is not needed with diff --git a/docs/source/getting_started.rst b/docs/source/getting_started.rst index bbe2d25d3b03..9b927097cfd2 100644 --- a/docs/source/getting_started.rst +++ b/docs/source/getting_started.rst @@ -44,6 +44,8 @@ easy to adopt mypy incrementally. In order to get useful diagnostics from mypy, you must add *type annotations* to your code. See the section below for details. +.. _getting-started-dynamic-vs-static: + Dynamic vs static typing ************************ diff --git a/docs/source/running_mypy.rst b/docs/source/running_mypy.rst index ffc04e6ea14c..c5222d9d5f47 100644 --- a/docs/source/running_mypy.rst +++ b/docs/source/running_mypy.rst @@ -228,6 +228,11 @@ attribute of the module will automatically succeed: # But this type checks, and x will have type 'Any' x = does_not_exist.foobar() +This can result in mypy failing to warn you about errors in your code. Since +operations on ``Any`` result in ``Any``, these dynamic types can propagate +through your code, making type checking less effective. See +:ref:`dynamic-typing` for more information. + The next sections describe what each of these errors means and recommended next steps; scroll to the section that matches your error. @@ -245,7 +250,7 @@ unless they either have declared themselves to be themselves on `typeshed `_, the repository of types for the standard library and some 3rd party libraries. -If you are getting this error, try: +If you are getting this error, try to obtain type hints for the library you're using: 1. Upgrading the version of the library you're using, in case a newer version has started to include type hints. @@ -264,7 +269,7 @@ If you are getting this error, try: adding the location to the ``MYPYPATH`` environment variable. These stub files do not need to be complete! A good strategy is to use - stubgen, a program that comes bundled with mypy, to generate a first + :ref:`stubgen `, a program that comes bundled with mypy, to generate a first rough draft of the stubs. You can then iterate on just the parts of the library you need. @@ -273,9 +278,11 @@ If you are getting this error, try: :ref:`PEP 561 compliant packages `. If you are unable to find any existing type hints nor have time to write your -own, you can instead *suppress* the errors. All this will do is make mypy stop -reporting an error on the line containing the import: the imported module -will continue to be of type ``Any``. +own, you can instead *suppress* the errors. + +All this will do is make mypy stop reporting an error on the line containing the +import: the imported module will continue to be of type ``Any``, and mypy may +not catch errors in its use. 1. To suppress a *single* missing import error, add a ``# type: ignore`` at the end of the line containing the import. diff --git a/docs/source/type_inference_and_annotations.rst b/docs/source/type_inference_and_annotations.rst index 5c58d56d85a1..6adb4e651224 100644 --- a/docs/source/type_inference_and_annotations.rst +++ b/docs/source/type_inference_and_annotations.rst @@ -185,6 +185,8 @@ Working around the issue is easy by adding a type annotation: a: list[int] = [] # OK foo(a) +.. _silencing-type-errors: + Silencing type errors ********************* @@ -228,6 +230,8 @@ short explanation of the bug. To do that, use this format: # Starting app on http://localhost:8000 app.run(8000) # type: ignore # `run()` in v2.0 accepts an `int`, as a port +Type ignore error codes +----------------------- By default, mypy displays an error code for each error: @@ -240,7 +244,21 @@ It is possible to add a specific error-code in your ignore comment (e.g. ``# type: ignore[attr-defined]``) to clarify what's being silenced. You can find more information about error codes :ref:`here `. -Similarly, you can also ignore all mypy errors in a file, by adding a +Other ways to silence errors +---------------------------- + +You can get mypy to silence errors about a specific variable by dynamically +typing it with ``Any``. See :ref:`dynamic-typing` for more information. + +.. code-block:: python + + from typing import Any + + def f(x: Any, y: str) -> None: + x = 'hello' + x += 1 # OK + +You can ignore all mypy errors in a file by adding a ``# mypy: ignore-errors`` at the top of the file: .. code-block:: python @@ -250,8 +268,28 @@ Similarly, you can also ignore all mypy errors in a file, by adding a import unittest ... +You can also specify per-module configuration options in your :ref:`config-file`. +For example: + +.. code-block:: ini + + # Don't report errors in the 'package_to_fix_later' package + [mypy-package_to_fix_later.*] + ignore_errors = True + + # Disable specific error codes in the 'tests' package + # Also don't require type annotations + [mypy-tests.*] + disable_error_code = var-annotated, has-type + allow_untyped_defs = True + + # Silence import errors from the 'library_missing_types' package + [mypy-library_missing_types.*] + ignore_missing_imports = True + Finally, adding a ``@typing.no_type_check`` decorator to a class, method or -function has the effect of ignoring that class, method or function. +function causes mypy to avoid type checking that class, method or function +and to treat it as not having any type annotations. .. code-block:: python