From d29aeb56bf844c24659dedf5d2f77a634a958e98 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Tue, 24 Aug 2021 12:22:31 +0300 Subject: [PATCH 1/3] Docs: adds a note about `issubclass` usage in `common_issues.rst` It was really not clear for some users that you can use `issubclass` as well. --- docs/source/common_issues.rst | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/docs/source/common_issues.rst b/docs/source/common_issues.rst index 58f8395091d0..09ccca8ee01d 100644 --- a/docs/source/common_issues.rst +++ b/docs/source/common_issues.rst @@ -392,7 +392,7 @@ explicit type cast: Mypy can't infer the type of ``o`` after the :py:class:`type() ` check because it only knows about :py:func:`isinstance` (and the latter is better -style anyway). We can write the above code without a cast by using +style anyway). We can write the above code without a cast by using :py:func:`isinstance`: .. code-block:: python @@ -402,7 +402,23 @@ style anyway). We can write the above code without a cast by using g(o + 1) # Okay; type of o is inferred as int here ... -Type inference in mypy is designed to work well in common cases, to be +Mypy can also use :py:func:`issubclass` +for better type inference when working with types: + +.. code-block:: python + + class MyCalcMeta(type): + @classmethod + def calc(cls) -> int: + ... + + def f(o: object) -> None: + t = type(o) # we must use a variable here + if issubtype(t, MyCalcMeta): # `issubtype(type(o), MyCalcMeta)` won't work + reveal_type(t) # Revealed type is "Type[MyCalcMeta]" + t.calc() # Okay + +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 From 3baaadc3bbf8a8d2455d291429a2b17d3673a626 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Tue, 31 Aug 2021 09:45:44 +0300 Subject: [PATCH 2/3] Update docs/source/common_issues.rst Co-authored-by: Terence Honles --- docs/source/common_issues.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/common_issues.rst b/docs/source/common_issues.rst index 09ccca8ee01d..fb395b0683b5 100644 --- a/docs/source/common_issues.rst +++ b/docs/source/common_issues.rst @@ -414,7 +414,7 @@ for better type inference when working with types: def f(o: object) -> None: t = type(o) # we must use a variable here - if issubtype(t, MyCalcMeta): # `issubtype(type(o), MyCalcMeta)` won't work + if issubclass(t, MyCalcMeta): # `issubclass(type(o), MyCalcMeta)` won't work reveal_type(t) # Revealed type is "Type[MyCalcMeta]" t.calc() # Okay From ce1d8d1b8c540d72a0510d0c8a0bb554815a6c02 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Sat, 4 Sep 2021 14:25:57 +0300 Subject: [PATCH 3/3] Improves "Common issues" page Things I've done: 1. Changed an outdated example to an original one from https://github.com/python/typing/issues/15#issuecomment-69130608 2. I've listed more type narrowing techniques, like `type(obj) is some_class` 3. Fixed several headers --- docs/source/common_issues.rst | 70 ++++++++++++++++------------------- 1 file changed, 32 insertions(+), 38 deletions(-) diff --git a/docs/source/common_issues.rst b/docs/source/common_issues.rst index fb395b0683b5..4de0ead6c7c5 100644 --- a/docs/source/common_issues.rst +++ b/docs/source/common_issues.rst @@ -367,19 +367,38 @@ above example: Complex type tests ------------------ -Mypy can usually infer the types correctly when using :py:func:`isinstance ` -type tests, but for other kinds of checks you may need to add an +Mypy can usually infer the types correctly when using :py:func:`isinstance `, +:py:func:`issubclass `, +or ``type(obj) is some_class`` type tests, +and even user-defined type guards, +but for other kinds of checks you may need to add an explicit type cast: .. code-block:: python - def f(o: object) -> None: - if type(o) is int: - o = cast(int, o) - g(o + 1) # This would be an error without the cast - ... - else: - ... + 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 `object` type, despite the fact that we know it is `str` + return cast(str, found) # So, we need an explicit cast to make mypy happy + +Alternatively, you can use ``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 `object` type, despite the fact that we know it is `str` + assert isinstance(found, str) # Now, `found` will be narrowed to `str` subtype + return found # No need for the explicit `cast()` anymore .. note:: @@ -390,33 +409,9 @@ explicit type cast: runtime. The cast above would have been unnecessary if the type of ``o`` was ``Any``. -Mypy can't infer the type of ``o`` after the :py:class:`type() ` check -because it only knows about :py:func:`isinstance` (and the latter is better -style anyway). We can write the above code without a cast by using -:py:func:`isinstance`: - -.. code-block:: python - - def f(o: object) -> None: - if isinstance(o, int): # Mypy understands isinstance checks - g(o + 1) # Okay; type of o is inferred as int here - ... - -Mypy can also use :py:func:`issubclass` -for better type inference when working with types: - -.. code-block:: python - - class MyCalcMeta(type): - @classmethod - def calc(cls) -> int: - ... +.. note:: - def f(o: object) -> None: - t = type(o) # we must use a variable here - if issubclass(t, MyCalcMeta): # `issubclass(type(o), MyCalcMeta)` won't work - reveal_type(t) # Revealed type is "Type[MyCalcMeta]" - t.calc() # Okay + You can read more about type narrowing techniques here. Type inference in Mypy is designed to work well in common cases, to be predictable and to let the type checker give useful error @@ -637,7 +632,7 @@ You can install the latest development version of mypy from source. Clone the sudo python3 -m pip install --upgrade . Variables vs type aliases ------------------------------------ +------------------------- Mypy has both type aliases and variables with types like ``Type[...]`` and it is important to know their difference. @@ -678,7 +673,7 @@ Mypy has both type aliases and variables with types like ``Type[...]`` and it is def fun2(x: tp) -> None: ... # error: Variable "__main__.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 @@ -789,7 +784,6 @@ False: If you use the :option:`--warn-unreachable ` flag, mypy will generate an error about each unreachable code block. - Narrowing and inner functions -----------------------------