Ваш анализ неверен - на самом деле это не имеет ничего общего с дисперсией, и тип Dict в mypy фактически не зависит от его значения.
Скорее проблема в том, что вы объявили значениевашего Dict, чтобы иметь тип Any
, динамический тип.Это фактически означает, что вы хотите, чтобы mypy просто не проверял тип ничего, связанного со значениями вашего Dict.И поскольку вы отказались от проверки типов, она, естественно, не обнаружит никаких ошибок, связанных с типами.
(Это достигается волшебным размещением Any
как вверху, так и внизу типа.решетка. В основном, для некоторого типа T
, это тот случай, когда Any
всегда является подтипом T , а T всегда является подтипом Any
. Mypy автоматически выбирает, какое отношение приводит кошибок нет.)
Вы можете увидеть, что Dict инвариантен для вас самих, запустив следующую программу:
from typing import Dict
class A: pass
class B(A): pass
class C(B): pass
def accepts_a(x: Dict[str, A]) -> None: pass
def accepts_b(x: Dict[str, B]) -> None: pass
def accepts_c(x: Dict[str, C]) -> None: pass
my_dict: Dict[str, B] = {"foo": B()}
# error: Argument 1 to "accepts_a" has incompatible type "Dict[str, B]"; expected "Dict[str, A]"
# note: "Dict" is invariant -- see http://mypy.readthedocs.io/en/latest/common_issues.html#variance
# note: Consider using "Mapping" instead, which is covariant in the value type
accepts_a(my_dict)
# Type checks! No error.
accepts_b(my_dict)
# error: Argument 1 to "accepts_c" has incompatible type "Dict[str, B]"; expected "Dict[str, C]"
accepts_c(my_dict)
Успешен только вызов accept_b
, что соответствует ожидаемой дисперсии.
Чтобы ответить на ваш вопрос о том, как установить дисперсию, mypy разработан таким образом, что дисперсия структур данных устанавливается во время определения и не может быть реально изменена во время вызова.
Таким образом, поскольку Dict был определен как инвариантный, вы не можете по-настоящему изменить его как ковариантный или инвариантный.
Подробнее о настройке дисперсии в defвремя начала, см. справочные документы mypy на дженерики .
Как вы указали, вы можете заявить, что хотите принять версию Dict только для чтенияс помощью картографии.Как правило, это тот случай, когда есть версия только для чтения любой структуры данных PEP 484, которую вы, возможно, захотите использовать - например, Sequence - версия List только для чтения.
AFAIK, по умолчанию нет версии только для записиDict хотя.Но вы можете взломать их вместе, используя протоколы , стандартизированный, скорее всего, стандартизированный метод выполнения структурного, а не номинального ввода:
from typing import Dict, TypeVar, Generic
from typing_extensions import Protocol
K = TypeVar('K', contravariant=True)
V = TypeVar('V', contravariant=True)
# Mypy requires the key to also be contravariant. I suspect this is because
# it cannot actually verify all types that satisfy the WriteOnlyDict
# protocol will use the key in an invariant way.
class WriteOnlyDict(Protocol, Generic[K, V]):
def __setitem__(self, key: K, value: V) -> None: ...
class A: pass
class B(A): pass
class C(B): pass
# All three functions accept only objects that implement the
# __setitem__ method with the signature described in the protocol.
#
# You can also use only this method inside of the function bodies,
# enforcing the write-only nature.
def accepts_a(x: WriteOnlyDict[str, A]) -> None: pass
def accepts_b(x: WriteOnlyDict[str, B]) -> None: pass
def accepts_c(x: WriteOnlyDict[str, C]) -> None: pass
my_dict: WriteOnlyDict[str, B] = {"foo": B()}
# error: Argument 1 to "accepts_a" has incompatible type "WriteOnlyDict[str, B]"; expected "WriteOnlyDict[str, A]"
accepts_a(my_dict)
# Both type-checks
accepts_b(my_dict)
accepts_c(my_dict)
Чтобы ответить на ваш неявный вопрос («Как заставить mypy обнаружить ошибку типа здесь / как правильно проверить тип моего кода?»), Ответ «простой» - просто избегайте использования Any
любой ценой.Каждый раз, когда вы это делаете, вы намеренно открываете дыру в системе типов.
Например, более безопасный для типов способ объявить, что значения вашего dict могут быть чем угодно, было бы использовать Dict[str, object]
.И теперь mypy пометил бы вызов функции add_items
как не типизированный.
Или, в качестве альтернативы, рассмотрите возможность использования TypedDict , если вы знаете, что ваши значения будут неоднородными.
Вы даже можете заставить mypy запретить использование некоторых Any, включив Отключить динамический набор семейство флагов командной строки / флагов файла конфигурации.
Тем не менее, на практикеполностью запретить использование Any часто нереально.Даже если вы можете встретить этот идеал в своем коде, многие сторонние библиотеки либо аннотированы, либо не полностью аннотированы, что означает, что они прибегают к использованию Any повсеместно.Так что, вообще, к сожалению, отказ от их использования, как правило, требует много дополнительной работы.