| .. _common_issues: |
| |
| Common issues and solutions |
| =========================== |
| |
| This section has examples of cases when you need to update your code |
| to use static typing, and ideas for working around issues if mypy |
| doesn't work as expected. Statically typed code is often identical to |
| normal Python code (except for type annotations), but sometimes you need |
| to do things slightly differently. |
| |
| .. _annotations_needed: |
| |
| 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>`. |
| |
| Example: |
| |
| .. code-block:: python |
| |
| 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: |
| |
| .. code-block:: python |
| |
| 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: |
| |
| **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 |
| |
| 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``. |
| |
| If you're having trouble debugging such situations, |
| :ref:`reveal_type() <reveal-type>` might come in handy. |
| |
| Note that sometimes library stubs with imprecise type information |
| can be a source of ``Any`` values. |
| |
| :py:meth:`__init__ <object.__init__>` **method has no annotated |
| arguments and no return type annotation.** |
| |
| This is basically a combination of the two cases above, in that ``__init__`` |
| without annotations can cause ``Any`` types leak into instance variables: |
| |
| .. code-block:: python |
| |
| class Bad: |
| def __init__(self): |
| self.value = "asdf" |
| 1 + "asdf" # No error! |
| |
| bad = Bad() |
| bad.value + 1 # No error! |
| reveal_type(bad) # Revealed type is "__main__.Bad" |
| reveal_type(bad.value) # Revealed type is "Any" |
| |
| class Good: |
| def __init__(self) -> None: # Explicitly return None |
| self.value = value |
| |
| |
| **Some imports may be silently ignored**. |
| |
| A common source of unexpected ``Any`` values is the |
| :option:`--ignore-missing-imports <mypy --ignore-missing-imports>` flag. |
| |
| When you use :option:`--ignore-missing-imports <mypy --ignore-missing-imports>`, |
| any imported module that cannot be found is silently replaced with ``Any``. |
| |
| To help debug this, simply leave out |
| :option:`--ignore-missing-imports <mypy --ignore-missing-imports>`. |
| As mentioned in :ref:`fix-missing-imports`, setting ``ignore_missing_imports=True`` |
| on a per-module basis will make bad surprises less likely and is highly encouraged. |
| |
| Use of the :option:`--follow-imports=skip <mypy --follow-imports>` flags can also |
| cause problems. Use of these flags is strongly discouraged and only required in |
| relatively niche situations. See :ref:`follow-imports` for more information. |
| |
| **mypy considers some of your code unreachable**. |
| |
| See :ref:`unreachable` for more information. |
| |
| **A function annotated as returning a non-optional type returns 'None' |
| and mypy doesn't complain**. |
| |
| .. code-block:: python |
| |
| def foo() -> str: |
| return None # No error! |
| |
| You may have disabled strict optional checking (see |
| :ref:`no_strict_optional` for more). |
| |
| .. _silencing_checker: |
| |
| Spurious errors and locally silencing the checker |
| ------------------------------------------------- |
| |
| You can use a ``# type: ignore`` comment to silence the type checker |
| on a particular line. For example, let's say our code is using |
| the C extension module ``frobnicate``, and there's no stub available. |
| Mypy will complain about this, as it has no information about the |
| module: |
| |
| .. code-block:: python |
| |
| import frobnicate # Error: No module "frobnicate" |
| frobnicate.start() |
| |
| You can add a ``# type: ignore`` comment to tell mypy to ignore this |
| error: |
| |
| .. code-block:: python |
| |
| import frobnicate # type: ignore |
| frobnicate.start() # Okay! |
| |
| The second line is now fine, since the ignore comment causes the name |
| ``frobnicate`` to get an implicit ``Any`` type. |
| |
| .. note:: |
| |
| You can use the form ``# type: ignore[<code>]`` to only ignore |
| specific errors on the line. This way you are less likely to |
| silence unexpected errors that are not safe to ignore, and this |
| will also document what the purpose of the comment is. See |
| :ref:`error-codes` for more information. |
| |
| .. note:: |
| |
| The ``# type: ignore`` comment will only assign the implicit ``Any`` |
| type if mypy cannot find information about that particular module. So, |
| if we did have a stub available for ``frobnicate`` then mypy would |
| ignore the ``# type: ignore`` comment and typecheck the stub as usual. |
| |
| Another option is to explicitly annotate values with type ``Any`` -- |
| mypy will let you perform arbitrary operations on ``Any`` |
| values. Sometimes there is no more precise type you can use for a |
| particular value, especially if you use dynamic Python features |
| such as :py:meth:`__getattr__ <object.__getattr__>`: |
| |
| .. code-block:: python |
| |
| class Wrapper: |
| ... |
| def __getattr__(self, a: str) -> Any: |
| return getattr(self._wrapped, a) |
| |
| Finally, you can create a stub file (``.pyi``) for a file that |
| generates spurious errors. Mypy will only look at the stub file |
| and ignore the implementation, since stub files take precedence |
| over ``.py`` files. |
| |
| Ignoring a whole file |
| --------------------- |
| |
| * To only ignore errors, use a top-level ``# mypy: ignore-errors`` comment instead. |
| * To only ignore errors with a specific error code, use a top-level |
| ``# mypy: disable-error-code="..."`` comment. Example: ``# mypy: disable-error-code="truthy-bool, ignore-without-code"`` |
| * To replace the contents of a module with ``Any``, use a per-module ``follow_imports = skip``. |
| See :ref:`Following imports <follow-imports>` for details. |
| |
| Note that a ``# type: ignore`` comment at the top of a module (before any statements, |
| including imports or docstrings) has the effect of ignoring the entire contents of the module. |
| This behaviour can be surprising and result in |
| "Module ... has no attribute ... [attr-defined]" errors. |
| |
| Issues with code at runtime |
| --------------------------- |
| |
| Idiomatic use of type annotations can sometimes run up against what a given |
| version of Python considers legal code. These can result in some of the |
| following errors when trying to run your code: |
| |
| * ``ImportError`` from circular imports |
| * ``NameError: name "X" is not defined`` from forward references |
| * ``TypeError: 'type' object is not subscriptable`` from types that are not generic at runtime |
| * ``ImportError`` or ``ModuleNotFoundError`` from use of stub definitions not available at runtime |
| * ``TypeError: unsupported operand type(s) for |: 'type' and 'type'`` from use of new syntax |
| |
| For dealing with these, see :ref:`runtime_troubles`. |
| |
| Mypy runs are slow |
| ------------------ |
| |
| If your mypy runs feel slow, you should probably use the :ref:`mypy |
| daemon <mypy_daemon>`, which can speed up incremental mypy runtimes by |
| a factor of 10 or more. :ref:`Remote caching <remote-cache>` can |
| make cold mypy runs several times faster. |
| |
| Types of empty collections |
| -------------------------- |
| |
| You often need to specify the type when you assign an empty list or |
| dict to a new variable, as mentioned earlier: |
| |
| .. code-block:: python |
| |
| a: List[int] = [] |
| |
| Without the annotation mypy can't always figure out the |
| precise type of ``a``. |
| |
| You can use a simple empty list literal in a dynamically typed function (as the |
| type of ``a`` would be implicitly ``Any`` and need not be inferred), if type |
| of the variable has been declared or inferred before, or if you perform a simple |
| modification operation in the same scope (such as ``append`` for a list): |
| |
| .. code-block:: python |
| |
| a = [] # Okay because followed by append, inferred type List[int] |
| for i in range(n): |
| a.append(i * i) |
| |
| However, in more complex cases an explicit type annotation can be |
| required (mypy will tell you this). Often the annotation can |
| make your code easier to understand, so it doesn't only help mypy but |
| everybody who is reading the code! |
| |
| Redefinitions with incompatible types |
| ------------------------------------- |
| |
| Each name within a function only has a single 'declared' type. You can |
| reuse for loop indices etc., but if you want to use a variable with |
| multiple types within a single function, you may need to instead use |
| multiple variables (or maybe declare the variable with an ``Any`` type). |
| |
| .. code-block:: python |
| |
| def f() -> None: |
| n = 1 |
| ... |
| n = 'x' # error: Incompatible types in assignment (expression has type "str", variable has type "int") |
| |
| .. note:: |
| |
| Using the :option:`--allow-redefinition <mypy --allow-redefinition>` |
| flag can suppress this error in several cases. |
| |
| Note that you can redefine a variable with a more *precise* or a more |
| concrete type. For example, you can redefine a sequence (which does |
| not support ``sort()``) as a list and sort it in-place: |
| |
| .. code-block:: python |
| |
| def f(x: Sequence[int]) -> None: |
| # Type of x is Sequence[int] here; we don't know the concrete type. |
| x = list(x) |
| # Type of x is List[int] here. |
| x.sort() # Okay! |
| |
| See :ref:`type-narrowing` for more information. |
| |
| .. _variance: |
| |
| Invariance vs covariance |
| ------------------------ |
| |
| Most mutable generic collections are invariant, and mypy considers all |
| user-defined generic classes invariant by default |
| (see :ref:`variance-of-generics` for motivation). This could lead to some |
| unexpected errors when combined with type inference. For example: |
| |
| .. code-block:: python |
| |
| class A: ... |
| class B(A): ... |
| |
| lst = [A(), A()] # Inferred type is List[A] |
| new_lst = [B(), B()] # inferred type is List[B] |
| lst = new_lst # mypy will complain about this, because List is invariant |
| |
| Possible strategies in such situations are: |
| |
| * Use an explicit type annotation: |
| |
| .. code-block:: python |
| |
| new_lst: List[A] = [B(), B()] |
| lst = new_lst # OK |
| |
| * Make a copy of the right hand side: |
| |
| .. code-block:: python |
| |
| lst = list(new_lst) # Also OK |
| |
| * Use immutable collections as annotations whenever possible: |
| |
| .. code-block:: python |
| |
| def f_bad(x: List[A]) -> A: |
| return x[0] |
| f_bad(new_lst) # Fails |
| |
| def f_good(x: Sequence[A]) -> A: |
| return x[0] |
| f_good(new_lst) # OK |
| |
| Declaring a supertype as variable type |
| -------------------------------------- |
| |
| Sometimes the inferred type is a subtype (subclass) of the desired |
| type. The type inference uses the first assignment to infer the type |
| of a name: |
| |
| .. code-block:: python |
| |
| class Shape: ... |
| class Circle(Shape): ... |
| class Triangle(Shape): ... |
| |
| shape = Circle() # mypy infers the type of shape to be Circle |
| shape = Triangle() # error: Incompatible types in assignment (expression has type "Triangle", variable has type "Circle") |
| |
| You can just give an explicit type for the variable in cases such the |
| above example: |
| |
| .. code-block:: python |
| |
| shape: Shape = Circle() # The variable s can be any Shape, not just Circle |
| shape = Triangle() # OK |
| |
| Complex type tests |
| ------------------ |
| |
| Mypy can usually infer the types correctly when using :py:func:`isinstance <isinstance>`, |
| :py:func:`issubclass <issubclass>`, |
| or ``type(obj) is some_class`` type tests, |
| and even :ref:`user-defined type guards <type-guards>`, |
| but for other kinds of checks you may need to add an |
| explicit type cast: |
| |
| .. code-block:: python |
| |
| from typing import Sequence, cast |
| |
| def find_first_str(a: Sequence[object]) -> str: |
| index = next((i for i, s in enumerate(a) if isinstance(s, str)), -1) |
| if index < 0: |
| raise ValueError('No str found') |
| |
| found = a[index] # Has type "object", despite the fact that we know it is "str" |
| return cast(str, found) # We need an explicit cast to make mypy happy |
| |
| Alternatively, you can use an ``assert`` statement together with some |
| of the supported type inference techniques: |
| |
| .. code-block:: python |
| |
| def find_first_str(a: Sequence[object]) -> str: |
| index = next((i for i, s in enumerate(a) if isinstance(s, str)), -1) |
| if index < 0: |
| raise ValueError('No str found') |
| |
| found = a[index] # Has type "object", despite the fact that we know it is "str" |
| assert isinstance(found, str) # Now, "found" will be narrowed to "str" |
| return found # No need for the explicit "cast()" anymore |
| |
| .. note:: |
| |
| Note that the :py:class:`object` type used in the above example is similar |
| to ``Object`` in Java: it only supports operations defined for *all* |
| objects, such as equality and :py:func:`isinstance`. The type ``Any``, |
| in contrast, supports all operations, even if they may fail at |
| runtime. The cast above would have been unnecessary if the type of |
| ``o`` was ``Any``. |
| |
| .. note:: |
| |
| You can read more about type narrowing techniques :ref:`here <type-narrowing>`. |
| |
| Type inference in Mypy is designed to work well in common cases, to be |
| predictable and to let the type checker give useful error |
| messages. More powerful type inference strategies often have complex |
| and difficult-to-predict failure modes and could result in very |
| confusing error messages. The tradeoff is that you as a programmer |
| sometimes have to give the type checker a little help. |
| |
| .. _version_and_platform_checks: |
| |
| Python version and system platform checks |
| ----------------------------------------- |
| |
| Mypy supports the ability to perform Python version checks and platform |
| checks (e.g. Windows vs Posix), ignoring code paths that won't be run on |
| the targeted Python version or platform. This allows you to more effectively |
| typecheck code that supports multiple versions of Python or multiple operating |
| systems. |
| |
| More specifically, mypy will understand the use of :py:data:`sys.version_info` and |
| :py:data:`sys.platform` checks within ``if/elif/else`` statements. For example: |
| |
| .. code-block:: python |
| |
| import sys |
| |
| # Distinguishing between different versions of Python: |
| if sys.version_info >= (3, 8): |
| # Python 3.8+ specific definitions and imports |
| else: |
| # Other definitions and imports |
| |
| # Distinguishing between different operating systems: |
| if sys.platform.startswith("linux"): |
| # Linux-specific code |
| elif sys.platform == "darwin": |
| # Mac-specific code |
| elif sys.platform == "win32": |
| # Windows-specific code |
| else: |
| # Other systems |
| |
| As a special case, you can also use one of these checks in a top-level |
| (unindented) ``assert``; this makes mypy skip the rest of the file. |
| Example: |
| |
| .. code-block:: python |
| |
| import sys |
| |
| assert sys.platform != 'win32' |
| |
| # The rest of this file doesn't apply to Windows. |
| |
| Some other expressions exhibit similar behavior; in particular, |
| :py:data:`~typing.TYPE_CHECKING`, variables named ``MYPY``, and any variable |
| whose name is passed to :option:`--always-true <mypy --always-true>` or :option:`--always-false <mypy --always-false>`. |
| (However, ``True`` and ``False`` are not treated specially!) |
| |
| .. note:: |
| |
| Mypy currently does not support more complex checks, and does not assign |
| any special meaning when assigning a :py:data:`sys.version_info` or :py:data:`sys.platform` |
| check to a variable. This may change in future versions of mypy. |
| |
| By default, mypy will use your current version of Python and your current |
| operating system as default values for :py:data:`sys.version_info` and |
| :py:data:`sys.platform`. |
| |
| To target a different Python version, use the :option:`--python-version X.Y <mypy --python-version>` flag. |
| For example, to verify your code typechecks if were run using Python 3.8, pass |
| in :option:`--python-version 3.8 <mypy --python-version>` from the command line. Note that you do not need |
| to have Python 3.8 installed to perform this check. |
| |
| To target a different operating system, use the :option:`--platform PLATFORM <mypy --platform>` flag. |
| For example, to verify your code typechecks if it were run in Windows, pass |
| in :option:`--platform win32 <mypy --platform>`. See the documentation for :py:data:`sys.platform` |
| for examples of valid platform parameters. |
| |
| .. _reveal-type: |
| |
| Displaying the type of an expression |
| ------------------------------------ |
| |
| You can use ``reveal_type(expr)`` to ask mypy to display the inferred |
| static type of an expression. This can be useful when you don't quite |
| understand how mypy handles a particular piece of code. Example: |
| |
| .. code-block:: python |
| |
| reveal_type((1, 'hello')) # Revealed type is "Tuple[builtins.int, builtins.str]" |
| |
| You can also use ``reveal_locals()`` at any line in a file |
| to see the types of all local variables at once. Example: |
| |
| .. code-block:: python |
| |
| a = 1 |
| b = 'one' |
| reveal_locals() |
| # Revealed local types are: |
| # a: builtins.int |
| # b: builtins.str |
| .. note:: |
| |
| ``reveal_type`` and ``reveal_locals`` are only understood by mypy and |
| don't exist in Python. If you try to run your program, you'll have to |
| remove any ``reveal_type`` and ``reveal_locals`` calls before you can |
| run your code. Both are always available and you don't need to import |
| them. |
| |
| .. _silencing-linters: |
| |
| Silencing linters |
| ----------------- |
| |
| In some cases, linters will complain about unused imports or code. In |
| these cases, you can silence them with a comment after type comments, or on |
| the same line as the import: |
| |
| .. code-block:: python |
| |
| # to silence complaints about unused imports |
| from typing import List # noqa |
| a = None # type: List[int] |
| |
| |
| To silence the linter on the same line as a type comment |
| put the linter comment *after* the type comment: |
| |
| .. code-block:: python |
| |
| a = some_complex_thing() # type: ignore # noqa |
| |
| Covariant subtyping of mutable protocol members is rejected |
| ----------------------------------------------------------- |
| |
| Mypy rejects this because this is potentially unsafe. |
| Consider this example: |
| |
| .. code-block:: python |
| |
| from typing_extensions import Protocol |
| |
| class P(Protocol): |
| x: float |
| |
| def fun(arg: P) -> None: |
| arg.x = 3.14 |
| |
| class C: |
| x = 42 |
| c = C() |
| fun(c) # This is not safe |
| c.x << 5 # Since this will fail! |
| |
| To work around this problem consider whether "mutating" is actually part |
| of a protocol. If not, then one can use a :py:class:`@property <property>` in |
| the protocol definition: |
| |
| .. code-block:: python |
| |
| from typing_extensions import Protocol |
| |
| class P(Protocol): |
| @property |
| def x(self) -> float: |
| pass |
| |
| def fun(arg: P) -> None: |
| ... |
| |
| class C: |
| x = 42 |
| fun(C()) # OK |
| |
| Dealing with conflicting names |
| ------------------------------ |
| |
| Suppose you have a class with a method whose name is the same as an |
| imported (or built-in) type, and you want to use the type in another |
| method signature. E.g.: |
| |
| .. code-block:: python |
| |
| class Message: |
| def bytes(self): |
| ... |
| def register(self, path: bytes): # error: Invalid type "mod.Message.bytes" |
| ... |
| |
| The third line elicits an error because mypy sees the argument type |
| ``bytes`` as a reference to the method by that name. Other than |
| renaming the method, a workaround is to use an alias: |
| |
| .. code-block:: python |
| |
| bytes_ = bytes |
| class Message: |
| def bytes(self): |
| ... |
| def register(self, path: bytes_): |
| ... |
| |
| Using a development mypy build |
| ------------------------------ |
| |
| You can install the latest development version of mypy from source. Clone the |
| `mypy repository on GitHub <https://github.com/python/mypy>`_, and then run |
| ``pip install`` locally: |
| |
| .. code-block:: text |
| |
| git clone https://github.com/python/mypy.git |
| cd mypy |
| python3 -m pip install --upgrade . |
| |
| To install a development version of mypy that is mypyc-compiled, see the |
| instructions at the `mypyc wheels repo <https://github.com/mypyc/mypy_mypyc-wheels>`_. |
| |
| Variables vs type aliases |
| ------------------------- |
| |
| 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. 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. 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 |
| |
| You can also use ``TypeAlias`` (:pep:`613`) to define an *explicit type alias*: |
| |
| .. code-block:: python |
| |
| from typing import TypeAlias # "from typing_extensions" in Python 3.9 and earlier |
| |
| class A: ... |
| Alias: TypeAlias = A |
| |
| 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 |
| |
| class A: ... |
| class B: ... |
| |
| if random() > 0.5: |
| Alias = A |
| else: |
| # error: Cannot assign multiple types to name "Alias" without an |
| # explicit "Type[...]" annotation |
| Alias = B |
| |
| 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: ... # OK |
| def fun2(x: tp) -> None: ... # Error: "tp" is not valid as a type |
| |
| Incompatible overrides |
| ---------------------- |
| |
| It's unsafe to override a method with a more specific argument type, |
| as it violates the `Liskov substitution principle |
| <https://stackoverflow.com/questions/56860/what-is-an-example-of-the-liskov-substitution-principle>`_. |
| For return types, it's unsafe to override a method with a more general |
| return type. |
| |
| Other incompatible signature changes in method overrides, such as |
| adding an extra required parameter, or removing an optional parameter, |
| will also generate errors. The signature of a method in a subclass |
| should accept all valid calls to the base class method. Mypy |
| treats a subclass as a subtype of the base class. An instance of a |
| subclass is valid everywhere where an instance of the base class is |
| valid. |
| |
| This example demonstrates both safe and unsafe overrides: |
| |
| .. code-block:: python |
| |
| from typing import Sequence, List, Iterable |
| |
| class A: |
| def test(self, t: Sequence[int]) -> Sequence[str]: |
| ... |
| |
| class GeneralizedArgument(A): |
| # A more general argument type is okay |
| def test(self, t: Iterable[int]) -> Sequence[str]: # OK |
| ... |
| |
| class NarrowerArgument(A): |
| # A more specific argument type isn't accepted |
| def test(self, t: List[int]) -> Sequence[str]: # Error |
| ... |
| |
| class NarrowerReturn(A): |
| # A more specific return type is fine |
| def test(self, t: Sequence[int]) -> List[str]: # OK |
| ... |
| |
| class GeneralizedReturn(A): |
| # A more general return type is an error |
| def test(self, t: Sequence[int]) -> Iterable[str]: # Error |
| ... |
| |
| You can use ``# type: ignore[override]`` to silence the error. Add it |
| to the line that generates the error, if you decide that type safety is |
| not necessary: |
| |
| .. code-block:: python |
| |
| class NarrowerArgument(A): |
| def test(self, t: List[int]) -> Sequence[str]: # type: ignore[override] |
| ... |
| |
| .. _unreachable: |
| |
| Unreachable code |
| ---------------- |
| |
| Mypy may consider some code as *unreachable*, even if it might not be |
| immediately obvious why. It's important to note that mypy will *not* |
| type check such code. Consider this example: |
| |
| .. code-block:: python |
| |
| class Foo: |
| bar: str = '' |
| |
| def bar() -> None: |
| foo: Foo = Foo() |
| return |
| x: int = 'abc' # Unreachable -- no error |
| |
| It's easy to see that any statement after ``return`` is unreachable, |
| and hence mypy will not complain about the mis-typed code below |
| it. For a more subtle example, consider this code: |
| |
| .. code-block:: python |
| |
| class Foo: |
| bar: str = '' |
| |
| def bar() -> None: |
| foo: Foo = Foo() |
| assert foo.bar is None |
| x: int = 'abc' # Unreachable -- no error |
| |
| Again, mypy will not report any errors. The type of ``foo.bar`` is |
| ``str``, and mypy reasons that it can never be ``None``. Hence the |
| ``assert`` statement will always fail and the statement below will |
| never be executed. (Note that in Python, ``None`` is not an empty |
| reference but an object of type ``None``.) |
| |
| In this example mypy will go on to check the last line and report an |
| error, since mypy thinks that the condition could be either True or |
| False: |
| |
| .. code-block:: python |
| |
| class Foo: |
| bar: str = '' |
| |
| def bar() -> None: |
| foo: Foo = Foo() |
| if not foo.bar: |
| return |
| x: int = 'abc' # Reachable -- error |
| |
| If you use the :option:`--warn-unreachable <mypy --warn-unreachable>` flag, mypy will generate |
| an error about each unreachable code block. |
| |
| Narrowing and inner functions |
| ----------------------------- |
| |
| Because closures in Python are late-binding (https://docs.python-guide.org/writing/gotchas/#late-binding-closures), |
| mypy will not narrow the type of a captured variable in an inner function. |
| This is best understood via an example: |
| |
| .. code-block:: python |
| |
| def foo(x: Optional[int]) -> Callable[[], int]: |
| if x is None: |
| x = 5 |
| print(x + 1) # mypy correctly deduces x must be an int here |
| def inner() -> int: |
| return x + 1 # but (correctly) complains about this line |
| |
| x = None # because x could later be assigned None |
| return inner |
| |
| inner = foo(5) |
| inner() # this will raise an error when called |
| |
| To get this code to type check, you could assign ``y = x`` after ``x`` has been |
| narrowed, and use ``y`` in the inner function, or add an assert in the inner |
| function. |