Вы можете сделать сами определения классов и функций, выполнив следующие действия:
from typing import Dict, Any
class AttrDict(Dict[str, Any]):
def __init__(self, *args: Any, **kwargs: Any) -> None:
super(AttrDict, self).__init__(*args, **kwargs)
self.__dict__ = self
def dict_to_attrdict(some: Dict[str, Any]) -> AttrDict:
return AttrDict(**some)
Наследование от Dict[X, Y]
ничем не отличается от наследования только от dict
во время выполнения, но оно дает mypy дополнительные метаданные, в которых он нуждается.
Однако на самом деле вы не сможете использовать экземпляр AttrDict
безопасным для типов способом: mypy всегда будет помечать такие вещи, как my_attrdict.foo
, как ошибку.
Это связано с тем, что невозможно статически определить, какие поля будут присутствовать в AttrDict
во всех случаях — mypy понятия не имеет, что именно находится внутри AttrDict
. И поскольку mypy не может сказать, действительно ли безопасно делать такие вещи, как my_attrdict.foo
, он склоняется к консервативной стороне и просто решает считать это небезопасным.
У вас есть два разных варианта решения этой проблемы. Во-первых, если вы действительно хотите, чтобы AttrDict
был как можно более динамичным, вы можете сказать mypy, чтобы он просто предполагал, что тип является любым произвольным динамическим типом, например:
from typing import Dict, Any, TYPE_CHECKING
if TYPE_CHECKING:
AttrDict = Any
else:
class AttrDict(dict):
def __init__(self, *args, **kwargs) -> None:
super(AttrDict, self).__init__(*args, **kwargs)
self.__dict__ = self
def dict_to_attrdict(some: Dict[str, Any]) -> AttrDict:
return AttrDict(**some)
TYPE_CHECKING
— это значение, которое всегда равно False во время выполнения, но mypy всегда обрабатывает его как True. Конечным результатом является то, что mypy будет рассматривать только ветвь «if» этого if/else и игнорировать все, что находится в ветке «else»: теперь мы научили mypy, что AttrDict
является псевдонимом типа для Any
: точно эквивалентно Any
. Однако во время выполнения мы всегда попадаем в ветку else и определяем класс, как раньше.
Основным недостатком этого подхода является то, что мы действительно не получаем никакой пользы от использования статической типизации. Мы можем добавить немного безопасности к dict_to_attrdict
, так как теперь мы можем принудительно указать, что ключи должны быть строками, но это все.
Второй вариант — использовать сильные стороны mypy и переписать свой код, чтобы он действительно использовал классы. Итак, мы бы избавились от AttrDict
и фактически использовали бы классы, которые устанавливают свои поля.
Это позволяет mypy понять, какие поля присутствуют, каковы их типы и т. д. Это немного больше работы заранее, но плюс в том, что mypy может дать вам более сильные гарантии правильности вашего кода.
Если вы считаете, что на самом деле определение группы классов с полями утомительно, попробуйте использовать новый модуль «dataclasses» (если вы используете Python 3.7) или сторонний модуль «attrs». Я считаю, что mypy недавно добавил поддержку для обоих.
Возможно, вам придется подождать, пока mypy 0.620 не будет выпущена в предстоящий вторник, если вы хотите использовать классы данных — я не помню, вошла ли эта функция в mypy 0.600 или mypy 0.610.
person
Michael0x2a
schedule
05.07.2018