Skip to content

Add type narrowing docs about callable #11174

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 1 commit into from
Sep 23, 2021
Merged
Changes from all commits
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
68 changes: 51 additions & 17 deletions docs/source/type_narrowing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ The simplest way to narrow a type is to use one of the supported expressions:
- :py:func:`isinstance` like in ``isinstance(obj, float)`` will narrow ``obj`` to have ``float`` type
- :py:func:`issubclass` like in ``issubclass(cls, MyClass)`` will narrow ``cls`` to be ``Type[MyClass]``
- :py:func:`type` like in ``type(obj) is int`` will narrow ``obj`` to have ``int`` type
- :py:func:`callable` like in ``callable(obj)`` will narrow object to callable type

Type narrowing is contextual. For example, based on the condition, mypy will narrow an expression only within an ``if`` branch:

Expand Down Expand Up @@ -57,6 +58,34 @@ We can also use ``assert`` to narrow types in the same context:
assert isinstance(arg, int)
reveal_type(arg) # Revealed type: "builtins.int"

.. note::

With :option:`--warn-unreachable <mypy --warn-unreachable>`
narrowing types to some impossible state will be treated as an error.

.. code-block:: python

def function(arg: int):
# error: Subclass of "int" and "str" cannot exist:
# would have incompatible method signatures
assert isinstance(arg, str)

# error: Statement is unreachable
print("so mypy concludes the assert will always trigger")

Without ``--warn-unreachable`` mypy will simply not check code it deems to be
unreachable. See :ref:`unreachable` for more information.

.. code-block:: python

x: int = 1
assert isinstance(x, str)
reveal_type(x) # Revealed type is "builtins.int"
print(x + '!') # Typechecks with `mypy`, but fails in runtime.

issubclass
~~~~~~~~~~

Mypy can also use :py:func:`issubclass`
for better type inference when working with types and metaclasses:

Expand All @@ -75,31 +104,36 @@ for better type inference when working with types and metaclasses:
reveal_type(t) # Revealed type is "Type[MyCalcMeta]"
t.calc() # Okay

.. note::
callable
~~~~~~~~

With :option:`--warn-unreachable <mypy --warn-unreachable>`
narrowing types to some impossible state will be treated as an error.
Mypy knows what types are callable and which ones are not during type checking.
So, we know what ``callable()`` will return. For example:

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

def function(arg: int):
# error: Subclass of "int" and "str" cannot exist:
# would have incompatible method signatures
assert isinstance(arg, str)
from typing import Callable

# error: Statement is unreachable
print("so mypy concludes the assert will always trigger")
x: Callable[[], int]

Without ``--warn-unreachable`` mypy will simply not check code it deems to be
unreachable. See :ref:`unreachable` for more information.
if callable(x):
reveal_type(x) # N: Revealed type is "def () -> builtins.int"
else:
... # Will never be executed and will raise error with `--warn-unreachable`

.. code-block:: python
``callable`` function can even split ``Union`` type
for callable and non-callable parts:

x: int = 1
assert isinstance(x, str)
reveal_type(x) # Revealed type is "builtins.int"
print(x + '!') # Typechecks with `mypy`, but fails in runtime.
.. code-block:: python

from typing import Callable, Union

x: Union[int, Callable[[], int]]

if callable(x):
reveal_type(x) # N: Revealed type is "def () -> builtins.int"
else:
reveal_type(x) # N: Revealed type is "builtins.int"

.. _casts:

Expand Down