ошибка mypy-несовместимый тип, несмотря на использование 'Union'


Рассмотрим следующий пример кода:

from typing import Dict, Union

def count_chars(string) -> Dict[str, Union[str, bool, int]]:
    result = {}  # type: Dict[str, Union[str, bool, int]]

    if isinstance(string, str) is False:
        result["success"] = False
        result["message"] = "Inavlid argument"
    else:
        result["success"] = True
        result["result"] = len(string)
    return result

def get_square(integer: int) -> int:
    return integer * integer

def validate_str(string: str) -> bool:
    check_count = count_chars(string)
    if check_count["success"] is False:
        print(check_count["message"])
        return False
    str_len_square = get_square(check_count["result"])
    return bool(str_len_square > 42)

result = validate_str("Lorem ipsum")

При запуске mypy с этим кодом возвращается следующая ошибка:

error: Argument 1 to "get_square" has incompatible type "Union[str, bool, int]"; expected "int"

И я не уверен, как я мог бы избежать этой ошибки без использования Dict[str, Any] в качестве возвращаемого типа в первой функции или установки расширения 'TypedDict' mypy. Является ли mypy на самом деле "правильным", любой мой код не является типобезопасным или это должно рассматриваться как ошибка mypy?

1 2

1 ответ:

Mypy здесь прав - если значения в вашем dict могут быть strs, ints или bools, то строго говоря, мы не можем предположить, что check_count["result"] всегда будет вычисляться точно int.

У вас есть несколько способов решить эту проблему. Первый способ-это на самом деле простопроверить Тип check_count["result"], чтобы увидеть, является ли это int. Это можно сделать с помощью assert:
assert isinstance(check_count["result"], int)
str_len_square = get_square(check_count["result"])

...или, возможно, утверждение if:

if isinstance(check_count["result"], int):
    str_len_square = get_square(check_count["result"])
else:
    # Throw some kind of exception here?

Mypy понимает проверки типа этой формы в утверждениях и утверждениях if (к a ограниченный объем).

Однако, это может стать утомительным разбрасыванием этих проверок по всему вашему коду. Так что, возможно, лучше всего просто отказаться от использования диктов и переключиться на использование классов.

То есть определите класс:

class Result:
    def __init__(self, success: bool, message: str) -> None:
        self.success = success
        self.message = message

...и верните экземпляр этого вместо этого.

Это несколько более неудобно в том смысле, что если ваша цель-в конечном счете вернуть / манипулировать json, вам теперь нужно написать код для преобразования этого класса из/в json, но это позволит вам избежать ошибки, связанные с типом.

Определение пользовательского класса может быть немного утомительным, поэтому вместо него можно попробовать использовать тип NamedTuple:

from typing import NamedTuple
Result = NamedTuple('Result', [('success', bool), ('message', str)])
# Use Result as a regular class

Вам все еще нужно написать код tuple -> json, и IIRC namedtuples (как обычная версия из модуля collections, так и этот типизированный вариант) менее эффективны, чем классы, но, возможно, это не имеет значения для вашего варианта использования.