| .. _getting-started: |
| |
| Getting started |
| =============== |
| |
| This chapter introduces some core concepts of mypy, including function |
| annotations, the :py:mod:`typing` module, stub files, and more. |
| |
| If you're looking for a quick intro, see the |
| :ref:`mypy cheatsheet <cheat-sheet-py3>`. |
| |
| If you're unfamiliar with the concepts of static and dynamic type checking, |
| be sure to read this chapter carefully, as the rest of the documentation |
| may not make much sense otherwise. |
| |
| Installing and running mypy |
| *************************** |
| |
| Mypy requires Python 3.8 or later to run. You can install mypy using pip: |
| |
| .. code-block:: shell |
| |
| $ python3 -m pip install mypy |
| |
| Once mypy is installed, run it by using the ``mypy`` tool: |
| |
| .. code-block:: shell |
| |
| $ mypy program.py |
| |
| This command makes mypy *type check* your ``program.py`` file and print |
| out any errors it finds. Mypy will type check your code *statically*: this |
| means that it will check for errors without ever running your code, just |
| like a linter. |
| |
| This also means that you are always free to ignore the errors mypy reports, |
| if you so wish. You can always use the Python interpreter to run your code, |
| even if mypy reports errors. |
| |
| However, if you try directly running mypy on your existing Python code, it |
| will most likely report little to no errors. This is a feature! It makes it |
| 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 |
| ************************ |
| |
| A function without type annotations is considered to be *dynamically typed* by mypy: |
| |
| .. code-block:: python |
| |
| def greeting(name): |
| return 'Hello ' + name |
| |
| By default, mypy will **not** type check dynamically typed functions. This means |
| that with a few exceptions, mypy will not report any errors with regular unannotated Python. |
| |
| This is the case even if you misuse the function! |
| |
| .. code-block:: python |
| |
| def greeting(name): |
| return 'Hello ' + name |
| |
| # These calls will fail when the program run, but mypy does not report an error |
| # because "greeting" does not have type annotations. |
| greeting(123) |
| greeting(b"Alice") |
| |
| We can get mypy to detect these kinds of bugs by adding *type annotations* (also |
| known as *type hints*). For example, you can tell mypy that ``greeting`` both accepts |
| and returns a string like so: |
| |
| .. code-block:: python |
| |
| # The "name: str" annotation says that the "name" argument should be a string |
| # The "-> str" annotation says that "greeting" will return a string |
| def greeting(name: str) -> str: |
| return 'Hello ' + name |
| |
| This function is now *statically typed*: mypy will use the provided type hints |
| to detect incorrect use of the ``greeting`` function and incorrect use of |
| variables within the ``greeting`` function. For example: |
| |
| .. code-block:: python |
| |
| def greeting(name: str) -> str: |
| return 'Hello ' + name |
| |
| greeting(3) # Argument 1 to "greeting" has incompatible type "int"; expected "str" |
| greeting(b'Alice') # Argument 1 to "greeting" has incompatible type "bytes"; expected "str" |
| greeting("World!") # No error |
| |
| def bad_greeting(name: str) -> str: |
| return 'Hello ' * name # Unsupported operand types for * ("str" and "str") |
| |
| Being able to pick whether you want a function to be dynamically or statically |
| typed can be very helpful. For example, if you are migrating an existing |
| Python codebase to use static types, it's usually easier to migrate by incrementally |
| adding type hints to your code rather than adding them all at once. Similarly, |
| when you are prototyping a new feature, it may be convenient to initially implement |
| the code using dynamic typing and only add type hints later once the code is more stable. |
| |
| Once you are finished migrating or prototyping your code, you can make mypy warn you |
| if you add a dynamic function by mistake by using the :option:`--disallow-untyped-defs <mypy --disallow-untyped-defs>` |
| flag. You can also get mypy to provide some limited checking of dynamically typed |
| functions by using the :option:`--check-untyped-defs <mypy --check-untyped-defs>` flag. |
| See :ref:`command-line` for more information on configuring mypy. |
| |
| Strict mode and configuration |
| ***************************** |
| |
| Mypy has a *strict mode* that enables a number of additional checks, |
| like :option:`--disallow-untyped-defs <mypy --disallow-untyped-defs>`. |
| |
| If you run mypy with the :option:`--strict <mypy --strict>` flag, you |
| will basically never get a type related error at runtime without a corresponding |
| mypy error, unless you explicitly circumvent mypy somehow. |
| |
| However, this flag will probably be too aggressive if you are trying |
| to add static types to a large, existing codebase. See :ref:`existing-code` |
| for suggestions on how to handle that case. |
| |
| Mypy is very configurable, so you can start with using ``--strict`` |
| and toggle off individual checks. For instance, if you use many third |
| party libraries that do not have types, |
| :option:`--ignore-missing-imports <mypy --ignore-missing-imports>` |
| may be useful. See :ref:`getting-to-strict` for how to build up to ``--strict``. |
| |
| See :ref:`command-line` and :ref:`config-file` for a complete reference on |
| configuration options. |
| |
| More complex types |
| ****************** |
| |
| So far, we've added type hints that use only basic concrete types like |
| ``str`` and ``float``. What if we want to express more complex types, |
| such as "a list of strings" or "an iterable of ints"? |
| |
| For example, to indicate that some function can accept a list of |
| strings, use the ``list[str]`` type (Python 3.9 and later): |
| |
| .. code-block:: python |
| |
| def greet_all(names: list[str]) -> None: |
| for name in names: |
| print('Hello ' + name) |
| |
| names = ["Alice", "Bob", "Charlie"] |
| ages = [10, 20, 30] |
| |
| greet_all(names) # Ok! |
| greet_all(ages) # Error due to incompatible types |
| |
| The :py:class:`list` type is an example of something called a *generic type*: it can |
| accept one or more *type parameters*. In this case, we *parameterized* :py:class:`list` |
| by writing ``list[str]``. This lets mypy know that ``greet_all`` accepts specifically |
| lists containing strings, and not lists containing ints or any other type. |
| |
| In the above examples, the type signature is perhaps a little too rigid. |
| After all, there's no reason why this function must accept *specifically* a list -- |
| it would run just fine if you were to pass in a tuple, a set, or any other custom iterable. |
| |
| You can express this idea using :py:class:`collections.abc.Iterable`: |
| |
| .. code-block:: python |
| |
| from collections.abc import Iterable # or "from typing import Iterable" |
| |
| def greet_all(names: Iterable[str]) -> None: |
| for name in names: |
| print('Hello ' + name) |
| |
| This behavior is actually a fundamental aspect of the PEP 484 type system: when |
| we annotate some variable with a type ``T``, we are actually telling mypy that |
| variable can be assigned an instance of ``T``, or an instance of a *subtype* of ``T``. |
| That is, ``list[str]`` is a subtype of ``Iterable[str]``. |
| |
| This also applies to inheritance, so if you have a class ``Child`` that inherits from |
| ``Parent``, then a value of type ``Child`` can be assigned to a variable of type ``Parent``. |
| For example, a ``RuntimeError`` instance can be passed to a function that is annotated |
| as taking an ``Exception``. |
| |
| As another example, suppose you want to write a function that can accept *either* |
| ints or strings, but no other types. You can express this using the |
| :py:data:`~typing.Union` type. For example, ``int`` is a subtype of ``Union[int, str]``: |
| |
| .. code-block:: python |
| |
| from typing import Union |
| |
| def normalize_id(user_id: Union[int, str]) -> str: |
| if isinstance(user_id, int): |
| return f'user-{100_000 + user_id}' |
| else: |
| return user_id |
| |
| The :py:mod:`typing` module contains many other useful types. |
| |
| For a quick overview, look through the :ref:`mypy cheatsheet <cheat-sheet-py3>`. |
| |
| For a detailed overview (including information on how to make your own |
| generic types or your own type aliases), look through the |
| :ref:`type system reference <overview-type-system-reference>`. |
| |
| .. note:: |
| |
| When adding types, the convention is to import types |
| using the form ``from typing import Union`` (as opposed to doing |
| just ``import typing`` or ``import typing as t`` or ``from typing import *``). |
| |
| For brevity, we often omit imports from :py:mod:`typing` or :py:mod:`collections.abc` |
| in code examples, but mypy will give an error if you use types such as |
| :py:class:`~typing.Iterable` without first importing them. |
| |
| .. note:: |
| |
| In some examples we use capitalized variants of types, such as |
| ``List``, and sometimes we use plain ``list``. They are equivalent, |
| but the prior variant is needed if you are using Python 3.8 or earlier. |
| |
| Local type inference |
| ******************** |
| |
| Once you have added type hints to a function (i.e. made it statically typed), |
| mypy will automatically type check that function's body. While doing so, |
| mypy will try and *infer* as many details as possible. |
| |
| We saw an example of this in the ``normalize_id`` function above -- mypy understands |
| basic :py:func:`isinstance <isinstance>` checks and so can infer that the ``user_id`` variable was of |
| type ``int`` in the if-branch and of type ``str`` in the else-branch. |
| |
| As another example, consider the following function. Mypy can type check this function |
| without a problem: it will use the available context and deduce that ``output`` must be |
| of type ``list[float]`` and that ``num`` must be of type ``float``: |
| |
| .. code-block:: python |
| |
| def nums_below(numbers: Iterable[float], limit: float) -> list[float]: |
| output = [] |
| for num in numbers: |
| if num < limit: |
| output.append(num) |
| return output |
| |
| For more details, see :ref:`type-inference-and-annotations`. |
| |
| Types from libraries |
| ******************** |
| |
| Mypy can also understand how to work with types from libraries that you use. |
| |
| For instance, mypy comes out of the box with an intimate knowledge of the |
| Python standard library. For example, here is a function which uses the |
| ``Path`` object from the |
| `pathlib standard library module <https://docs.python.org/3/library/pathlib.html>`_: |
| |
| .. code-block:: python |
| |
| from pathlib import Path |
| |
| def load_template(template_path: Path, name: str) -> str: |
| # Mypy knows that `template_path` has a `read_text` method that returns a str |
| template = template_path.read_text() |
| # ...so it understands this line type checks |
| return template.replace('USERNAME', name) |
| |
| If a third party library you use :ref:`declares support for type checking <installed-packages>`, |
| mypy will type check your use of that library based on the type hints |
| it contains. |
| |
| However, if the third party library does not have type hints, mypy will |
| complain about missing type information. |
| |
| .. code-block:: text |
| |
| prog.py:1: error: Library stubs not installed for "yaml" |
| prog.py:1: note: Hint: "python3 -m pip install types-PyYAML" |
| prog.py:2: error: Library stubs not installed for "requests" |
| prog.py:2: note: Hint: "python3 -m pip install types-requests" |
| ... |
| |
| In this case, you can provide mypy a different source of type information, |
| by installing a *stub* package. A stub package is a package that contains |
| type hints for another library, but no actual code. |
| |
| .. code-block:: shell |
| |
| $ python3 -m pip install types-PyYAML types-requests |
| |
| Stubs packages for a distribution are often named ``types-<distribution>``. |
| Note that a distribution name may be different from the name of the package that |
| you import. For example, ``types-PyYAML`` contains stubs for the ``yaml`` |
| package. |
| |
| For more discussion on strategies for handling errors about libraries without |
| type information, refer to :ref:`fix-missing-imports`. |
| |
| For more information about stubs, see :ref:`stub-files`. |
| |
| Next steps |
| ********** |
| |
| If you are in a hurry and don't want to read lots of documentation |
| before getting started, here are some pointers to quick learning |
| resources: |
| |
| * Read the :ref:`mypy cheatsheet <cheat-sheet-py3>`. |
| |
| * Read :ref:`existing-code` if you have a significant existing |
| codebase without many type annotations. |
| |
| * Read the `blog post <https://blog.zulip.org/2016/10/13/static-types-in-python-oh-mypy/>`_ |
| about the Zulip project's experiences with adopting mypy. |
| |
| * If you prefer watching talks instead of reading, here are |
| some ideas: |
| |
| * Carl Meyer: |
| `Type Checked Python in the Real World <https://www.youtube.com/watch?v=pMgmKJyWKn8>`_ |
| (PyCon 2018) |
| |
| * Greg Price: |
| `Clearer Code at Scale: Static Types at Zulip and Dropbox <https://www.youtube.com/watch?v=0c46YHS3RY8>`_ |
| (PyCon 2018) |
| |
| * Look at :ref:`solutions to common issues <common_issues>` with mypy if |
| you encounter problems. |
| |
| * You can ask questions about mypy in the |
| `mypy issue tracker <https://github.com/python/mypy/issues>`_ and |
| typing `Gitter chat <https://gitter.im/python/typing>`_. |
| |
| * For general questions about Python typing, try posting at |
| `typing discussions <https://github.com/python/typing/discussions>`_. |
| |
| You can also continue reading this document and skip sections that |
| aren't relevant for you. You don't need to read sections in order. |