Stati c тип анализа с Python dicts - PullRequest
3 голосов
/ 18 февраля 2020

Может кто-нибудь объяснить, почему этот код, хотя и действительный, заставляет анализатор mypy stati c жаловаться несколькими способами:

ranges = dict()
ranges['max'] = 0
ranges['services'] = []
ranges['services'].append('a')

А именно:

error: Incompatible types in assignment (expression has type "List[<nothing>]", target has type "int")

error: "int" has no attribute "append"

Если я просто добавлю подсказка типа для начальной переменной ranges: dict = dict() работает нормально.

Меня смущает, почему анализатор stati c не может решить это сам, тем более, что я использую ключевое слово dict для инициализации dict в первом случае.

Ответы [ 2 ]

4 голосов
/ 18 февраля 2020

Словари обычно используются в качестве наборов ключей, наиболее важной операцией является поиск значения, связанного с произвольным ключом . Обычно в словаре каждый ключ имеет одинаковый тип, а каждое значение имеет одинаковый тип; если значения неоднородны, то выражение ranges[key] не обязательно будет иметь определенный тип (хотя вы можете представить его как объединение).

В вашем коде анализатор stati c пытается определить тип вашего словаря. Тип, который он ожидает, имеет форму Dict[K, V], где K и V еще предстоит определить. Первое назначение ranges['max'] = 0 дает информацию об обоих неизвестных: K кажется str и V кажется int. Таким образом, на этом этапе ranges выводится как тип Dict[str, int].

Следующие две строки дают ошибки, потому что пустой список не может использоваться как значение в Dict[str, int], а значения из Dict[str, int] нет метода append.

Явная аннотация типа ranges: dict = dict() отменяет поведение по умолчанию, указывая, что это разнородный словарь, так что значения не все имеют иметь тот же тип. Учитывая эту информацию, анализатор stati c не предполагает, что, поскольку одним из значений является int, все они должны быть int s.

3 голосов
/ 18 февраля 2020

Поскольку вы не предоставили никаких аннотаций, mypy должен попытаться вывести типы. Есть несколько вариантов выбора:

  • ranges = dict() подразумевает range: Dict[Any, Any]

    Это позволит принять весь ваш код. Он также принял бы ranges['services'].keys() или любую другую операцию, поскольку он удаляет всю информацию о типах.

  • ranges['max'] = 0 подразумевает range: Dict[str, int]

    Это то, что mypy выбирает , Он отклоняет любые ключи, которые не являются целыми числами, в частности c ваш list.

  • ranges['services'] = [] подразумевает range: Dict[str, list]

    Это принимает только ваши ranges['services'] поле. ranges['max'] является недействительным тогда, учитывая, что это не list.

  • ranges['max'] = 0 и ranges['services'] = [] подразумевает range: Dict[str, Union[int, list]]

    Принимает присвоение значений. Он отклоняет ranges['services'].append('a'), поскольку ranges['services'] может быть int.

В принципе, ни одна из этих работ. С простым Dict[K, V] ваш код не может быть напечатан правильно.


Вы можете использовать typing.TypedDict для явного аннотирования ranges. Поскольку dict, как правило, не ограничивается парами «ключ-значение», mypy не будет выводить это сам по себе.

class Ranges(TypedDict):
    max: int
    services: List[str]

ranges: Ranges = dict()
...