Существует ли аннотация типа python, подходящая для вложенных JSONish-диктовок со строковыми ключами?

#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. Без хотя бы ссылки на библиотеку это на самом деле не отвечает на вопрос.