From d5e9302f5b7d2238d99ce17c82846a76b62b7966 Mon Sep 17 00:00:00 2001 From: Ilya Priven Date: Sat, 2 Sep 2023 00:16:12 -0400 Subject: [PATCH 1/3] Document we're not tracking relationships between symbols --- docs/source/type_narrowing.rst | 43 ++++++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/docs/source/type_narrowing.rst b/docs/source/type_narrowing.rst index 4bc0fda70138..552c8c788b91 100644 --- a/docs/source/type_narrowing.rst +++ b/docs/source/type_narrowing.rst @@ -3,7 +3,7 @@ Type narrowing ============== -This section is dedicated to several type narrowing +This section is dedicated to several type narrowing techniques which are supported by mypy. Type narrowing is when you convince a type checker that a broader type is actually more specific, for instance, that an object of type ``Shape`` is actually of the narrower type ``Square``. @@ -14,10 +14,11 @@ Type narrowing expressions 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:class:`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 +- :py:func:`isinstance` like in :code:`isinstance(obj, float)` will narrow ``obj`` to have ``float`` type +- :py:func:`issubclass` like in :code:`issubclass(cls, MyClass)` will narrow ``cls`` to be ``Type[MyClass]`` +- :py:class:`type` like in :code:`type(obj) is int` will narrow ``obj`` to have ``int`` type +- :py:func:`callable` like in :code:`callable(obj)` will narrow object to callable type +- :code:`obj is not None` will narrow object to its :ref:`non-optional form ` Type narrowing is contextual. For example, based on the condition, mypy will narrow an expression only within an ``if`` branch: @@ -83,6 +84,38 @@ We can also use ``assert`` to narrow types in the same context: reveal_type(x) # Revealed type is "builtins.int" print(x + '!') # Typechecks with `mypy`, but fails in runtime. + +Limitations of narrowing +~~~~~~~~~~~~~~~~~~~~~~~~ + +Mypy's analysis is limited to individual symbols and it will not track +relationships between symbols. For example, in the following code +it's easy to deduce that if :code:`a` is None then :code:`b` must not be, +therefore :code:`a or b` will always be a string, but Mypy will not be able to tell that: + +.. code-block:: python + + def f(a: str | None, b: str | None) -> str: + if a is not None or b is not None: + return a or b # Incompatible return value type (got "str | None", expected "str") + return 'spam' + +Tracking these sort of cross-variable conditions in a type checker would add significant complexity +and performance overhead and would be computationally infeasible in all but the most basic cases. + +You may override the type checker with a :ref:`cast `, or rewrite the function to be +slightly more verbose: + +.. code-block:: python + + def f(a: str | None, b: str | None) -> str: + if a is not None: + return a + elif b is not None: + return b + return 'spam' + + issubclass ~~~~~~~~~~ From 7b0b104896d27eb5e4c1aced61f9555cddaef3f9 Mon Sep 17 00:00:00 2001 From: Ilya Priven Date: Sat, 2 Sep 2023 16:57:31 -0400 Subject: [PATCH 2/3] move to separate section, wordsmithing --- docs/source/type_narrowing.rst | 61 +++++++++++++++++----------------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/docs/source/type_narrowing.rst b/docs/source/type_narrowing.rst index 552c8c788b91..aa61e8407d2c 100644 --- a/docs/source/type_narrowing.rst +++ b/docs/source/type_narrowing.rst @@ -85,37 +85,6 @@ We can also use ``assert`` to narrow types in the same context: print(x + '!') # Typechecks with `mypy`, but fails in runtime. -Limitations of narrowing -~~~~~~~~~~~~~~~~~~~~~~~~ - -Mypy's analysis is limited to individual symbols and it will not track -relationships between symbols. For example, in the following code -it's easy to deduce that if :code:`a` is None then :code:`b` must not be, -therefore :code:`a or b` will always be a string, but Mypy will not be able to tell that: - -.. code-block:: python - - def f(a: str | None, b: str | None) -> str: - if a is not None or b is not None: - return a or b # Incompatible return value type (got "str | None", expected "str") - return 'spam' - -Tracking these sort of cross-variable conditions in a type checker would add significant complexity -and performance overhead and would be computationally infeasible in all but the most basic cases. - -You may override the type checker with a :ref:`cast `, or rewrite the function to be -slightly more verbose: - -.. code-block:: python - - def f(a: str | None, b: str | None) -> str: - if a is not None: - return a - elif b is not None: - return b - return 'spam' - - issubclass ~~~~~~~~~~ @@ -392,3 +361,33 @@ What happens here? .. note:: The same will work with ``isinstance(x := a, float)`` as well. + +Limitations +----------- + +Mypy's analysis is limited to individual symbols and it will not track +relationships between symbols. For example, in the following code +it's easy to deduce that if :code:`a` is None then :code:`b` must not be, +therefore :code:`a or b` will always be a string, but Mypy will not be able to tell that: + +.. code-block:: python + + def f(a: str | None, b: str | None) -> str: + if a is not None or b is not None: + return a or b # Incompatible return value type (got "str | None", expected "str") + return 'spam' + +Tracking these sort of cross-variable conditions in a type checker would add significant complexity +and performance overhead. + +You may override the type checker with a :ref:`cast `, help it with an assertion, +or rewrite the function to be slightly more verbose: + +.. code-block:: python + + def f(a: str | None, b: str | None) -> str: + if a is not None: + return a + elif b is not None: + return b + return 'spam' From c2f9af19c93706b5afacd3ef49ff983e68ab6902 Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Sat, 2 Sep 2023 14:13:22 -0700 Subject: [PATCH 3/3] Update docs/source/type_narrowing.rst --- docs/source/type_narrowing.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/type_narrowing.rst b/docs/source/type_narrowing.rst index aa61e8407d2c..4c5c2851edd0 100644 --- a/docs/source/type_narrowing.rst +++ b/docs/source/type_narrowing.rst @@ -380,7 +380,7 @@ therefore :code:`a or b` will always be a string, but Mypy will not be able to t Tracking these sort of cross-variable conditions in a type checker would add significant complexity and performance overhead. -You may override the type checker with a :ref:`cast `, help it with an assertion, +You can use an ``assert`` to convince the type checker, override it with a :ref:`cast ` or rewrite the function to be slightly more verbose: .. code-block:: python