Почему mypy отклоняет мое объявление типа "смешанное объединение"? - PullRequest
2 голосов
/ 14 июля 2020

Во время устранения неполадок, связанных с частично связанной проблемой в Python чате , я обнаружил в mypy поведение, которое я не понимаю.

from typing import Union, List, Dict

def f(x: Union[
            Dict[str, float],
            Dict[str, str],
            Dict[str, int],
    ]):
        pass

f({"a": 1})     #passes
f({"a": "b"})   #passes
f({"a": 1.0})   #passes

def g(x: Union[
            Dict[str, float],
            Dict[str, Union[str, int]],
    ]):
        pass

g({"a": 1})     #fails
g({"a": "b"})   #fails
g({"a": 1.0})   #passes

def h(x: Dict[str, Union[float, str, int]]):
    pass

h({"a": 1})     #passes
h({"a": "b"})   #passes
h({"a": 1.0})   #passes

Когда я выполняю mypy в этом скрипте он жалуется только на среднюю функцию, g:

C:\Users\Kevin\Desktop>mypy test.py
test.py:20: error: Argument 1 to "g" has incompatible type "Dict[str, int]"; expected "Union[Dict[str, float], Dict[str, Union[str, int]]]"
test.py:20: note: "Dict" is invariant -- see http://mypy.readthedocs.io/en/latest/common_issues.html#variance
test.py:20: note: Consider using "Mapping" instead, which is covariant in the value type
test.py:21: error: Argument 1 to "g" has incompatible type "Dict[str, str]"; expected "Union[Dict[str, float], Dict[str, Union[str, int]]]"
test.py:21: note: "Dict" is invariant -- see http://mypy.readthedocs.io/en/latest/common_issues.html#variance
test.py:21: note: Consider using "Mapping" instead, which is covariant in the value type
Found 2 errors in 1 file (checked 1 source file)

(как следует из примечаний, замена Dict на Mapping удаляет ошибки, но, скажем, ради вопрос, что я должен использовать Dict.)

Эти ошибки меня удивляют. Насколько я могу судить, аннотации типов для каждой функции должны упроститься до той же группы типов: dict, ключи которого являются строками, а значения - числами с плавающей запятой / строками / целыми числами. Так почему только g имеет несовместимые типы? Mypy как-то смущает наличие двух Союзов?

1 Ответ

1 голос
/ 14 июля 2020

Это потому, что Dict инвариантен.

Dict[str, int] не является подтипом Dict[str, Union[str, int]] (хотя int является подтипом Union[int, str])

What если вы собираетесь сделать что-то вроде этого:

d: Dict[str, Union[str, int]]
u: Dict[str, int]
d = u  # Mypy error: Incompatible type
d[‘Key’].upper()

Mypy предполагает, что словари однородны: они всегда будут содержать только один тип типа. В отличие от этого, например, Tuples предназначены для хранения разнородных данных: каждый элемент может иметь другой тип.

Если вам нужен гетерогенный Dict, вы можете использовать TypedDict, но ожидается только фиксированный набор строковых ключей:

from typing import List, TypedDict

Mytype = TypedDict('Mytype', {'x': str, 'a': List[str]})
s: Mytype = {"x": "y", "a": ["b"]}

s['a'].append('c')

ПРИМЕЧАНИЕ:

Если вы не используете Python 3.8 или новее (где TypedDict доступен в стандартном модуле набора текста библиотеки) вам необходимо установить typing_extensions с помощью pip, чтобы использовать TypedDict

...