#python
#python
Вопрос:
Я хотел бы аннотировать определенные структуры данных в моем проекте, которые являются результатом декодирования некоторых данных JSON.
Лист прост:
ValueType = Union[int,str,float]
Ключи — это просто строки, так что это тоже просто:
str
Но проблема в том, что мои структуры могут быть вложенными, поэтому я хотел бы сделать что-то вроде следующего (но без явно неудачной ссылки на себя):
ValueType = Union[int,str,float]
NestedDictType = Mapping[str,Union[NestedDictType,ValueType]] # Fails!
Я полагаю, я мог бы создать что-то в цикле, но выполнение чего-то (сумасшедшего), подобного этому, в основном сводит на нет смысл аннотаций типа, поскольку структура NestedDictType не может быть определена статически.
from typing import Mapping, Type, Union
ValueType = Union[int, str, float]
def make_nested_dict_type(depth: int) -> Type:
# Does not actually work, sorry!
valueType = Union[make_nested_dict_type(depth - 1)] if depth else ValueType
return Mapping[str, valueType]
NestedDictType: Type = make_nested_dict_type(4)
foo: NestedDictType = {"a": {"b": {"c": 3, "d": "four", "e": 5.6}}}
Итак, как я могу кратко написать аннотацию такого типа?
Комментарии:
1. Эй, не могли бы вы предоставить нам формат ваших данных JSON и пример того, какой вы хотели бы видеть свою структуру данных?
2.
mypy
(пока) не поддерживает рекурсивные типы. В противном случае вы можете использовать прямую ссылку:NestedDictType = Mapping[str,Union["NestedDictType",ValueType]]
.3. Я думаю, что @chepner дает неудачный ответ — нет смысла предоставлять эту аннотацию, просто потому, что нет ничего, что могло бы это подтвердить.
4. Эй, посмотри на мой ответ. Вы структурируете свой код на основе типизированных / проверяемых типов данных, а затем при переходе в / из json используете typedload, который выполняет проверки во время выполнения.
Ответ №1:
Похоже, что начиная с Python 3.7, это не на 100% практично:
Система ввода может определять рекурсивную структуру типа (используя прямые ссылки на строку), однако MyPy и PyCharm обрабатывают рекурсивные части типа, как если бы они были любыми.
Самое близкое, что я смог получить, это вручную расширить рекурсивный тип до нескольких уровней. Вот пример, который частично удался.
import datetime
from typing import Mapping, Union, Any
# Recursive structure has been manually expanded to 3 levels, with the forward ref removed.
NestedDictType = Mapping[str,Union[int,str,float,Mapping[str,Union[int,str,float,Mapping[str,Union[int,str,float,Any]]]]]]
# MyPy correctly determines that this is valid
foo: NestedDictType = {"a": {"b": {"c": 3, "d": "four", "e": datetime.date.today()}}}
# MyPy correctly determines that this is bogus
bar: NestedDictType = {"a": datetime.date.today()}
# MyPy cannot detect the error because it's > 3 depth
baz: NestedDictType = {"a":{"b":{"c":{"d":datetime.date.today()}}}}
Ответ №2:
Я написал typedload библиотеку специально для этого. Работает с NamedTuple, классами данных или ссылками.
class A(NamedTuple):
field: str
field_2: int
field_3: Tuple[int, ...]
class B(NamedTuple):
field: A
@dataclass
class C:
field: B
Затем я использую свою библиотеку для преобразования dicts / списков в эти классы и наоборот.
typedload.load(data, C)
typedload.dump(data)
Комментарии:
1. Без хотя бы ссылки на библиотеку это на самом деле не отвечает на вопрос.