Comments 6
mypy меня не особо интересует, потому что там в самом mypy ворох нерешённых проблем, взять хотя бы рекурсивные типы. Если следовать всем канонам mypy, код на Python визуально превращается в нечитабельную кашу из монстроузных аннотаций типов, каких-то бессмысленных Generic, TypeVar и т. д. при всём при этом он остаётся всё таким же динамически типизированным и таким же медленным, только писать и читать его становится дольше и скучнее. Я уж лучше на Rust тогда писать буду с нормальной системой типов, безопасностью памяти, высокой производительностью и т.д., чем буду тащить весь этот "типизированный" мусор в Python и мучиться с mypy. Более того, огромное кол-во популярных библиотек вообще плевать хотели на mypy и их приходится добавлять в игнорируемые в конфиге, что сводит на нет хоть какой-то профит от mypy.
Поэтому я использую аннотации типов в Python исключительно для документирования, помощи IDE для построения модели кода и в pydantic/FastAPI.
А pydantic (и ругаемый вами FastAPI) берет лучшее от аннотаций типов и позволяет решать с помощью них реальные практические задачи.
К pydantic у меня претензии не к необязательному Optional, а к тому, что там Strict типы не используются по умолчанию. Об этом у них кстати тоже висит issue.
каких-то бессмысленных Generic, TypeVar и т. д.
Ну, лично для меня они не бессмысленны, а очень сильно упрощают чтение кода, особенно чужого.
огромное кол-во популярных библиотек вообще плевать хотели на mypy
Это дело времени, со временем многие библиотеки подтянутся. А какие не подтянутся — там сообщество само запилит аннотации (для того же Django уже есть, например).
И в mypy баги когда-нибудь пофиксятся, и рекурсивные типы когда-нибудь завезут, и светлое будущее обязательно наступит.
А библиотеки вроде pydantic, объявляя несовместимость с системой типов mypy не багом, а фичей, отдаляют это светлое будущее и попадают в мой личный чёрный список.
Но в целом лучше конечно на Rust переходить. :)
Тот же изначальный пример, но с mashumaro работает. Нужно только добавить миксин (можно для удобства в какой-нибудь базовый класс BaseConfig
):
from dataclasses import dataclass
from enum import Enum
from typing import Optional
from mashumaro import DataClassYAMLMixin
class Color(Enum):
RED = "red"
GREEN = "green"
BLUE = "blue"
@dataclass
class BattleStationConfig(DataClassYAMLMixin):
@dataclass
class Processor(DataClassYAMLMixin):
core_count: int
manufacturer: str
processor: Processor
memory_gb: int
led_color: Optional[Color] = None
yaml = """
processor:
core_count: 8
manufacturer: Intel
memory_gb: 8
led_color: red
"""
print(BattleStationConfig.from_yaml(yaml))
# BattleStationConfig(processor=BattleStationConfig.Processor(core_count=8, manufacturer='Intel'), memory_gb=8, led_color=<Color.RED: 'red'>)
Не нужно использовать ForwardRef
в качестве аннотации. Просто указывайте тип в кавычках и все будет работать автоматом.
Так же я бы советовал не вкладывать классы внутрь классов без особой нужды, диктуемой некоторыми фреймворками.
Пользуясь случаем, так же хочу предложить свою библиотеку для парсинга датаклассов: dataclass-factory. При её использовании не требуется менять иерархию классов (как в случае c pydantic или mashumaro), но при необходимости все ещё можно гибко настраивать поведение парсинга с помощью опциональных "схем".
В таком случае я бы переписал указанный код в виде:
from dataclasses import dataclass
from enum import Enum
from typing import Optional
from dataclass_factory import Factory
from yaml import load, SafeLoader
class Color(Enum):
RED = "red"
GREEN = "green"
BLUE = "blue"
@dataclass
class BattleStationConfig:
processor: "Processor" # вместо `ForwardRef`
memory_gb: int
led_color: Optional[Color] = None
@dataclass
class Processor:
core_count: int
manufacturer: str
yaml = """
processor:
core_count: 8
manufacturer: Intel
memory_gb: 8
led_color: red
"""
factory = Factory() # здесь задаются дополнительные настройки обработки
loaded = load(yaml, Loader=SafeLoader)
print(factory.load(loaded, BattleStationConfig))
# BattleStationConfig(processor=Processor(core_count=8, manufacturer='Intel'), memory_gb=8, led_color=<Color.RED: 'red'>)
Строгая десериализация YAML в Python c библиотекой marshmallow