Именованный кортеж из словаря с использованием оператора двойной звезды: также распакованы ли вложенные поля?

У меня есть два класса: Top и Nested, и для их создания мне нужно предоставить объекты TopDefinition и NestedDefinition, которые имеют тип NamedTuple (определения необходимы для аннотаций типов). А Class Top содержит атрибут, который представляет собой список объектов Nested instance.

Существует вложенный dict, который используется для создания экземпляра именованного кортежа. Входной дикт item выглядит как ниже:

type =<class 'dict'>
value={'t1': 'qwe', 't2': 'QWE', 't3': [{'n1': 'aaa', 'n2': 1}, {'n1': 'bb', 'n2': 3}]} 

Затем распаковывается, чтобы создать экземпляр класса TopDefinition с кодом

q = Top(top=TopDefinition(**item))для использования в качестве входных данных для создания экземпляра класса Top. и это хорошо работает, позже я могу увидеть в типе класса q и значение входного параметра:

type=<class '__main__.TopDefinition'>
value=TopDefinition(t1='qwe', t2='QWE', t3=[{'n1': 'aaa', 'n2': 1}, {'n1': 'bb', 'n2': 3}])

этот экземпляр TopDefinition правильно создан как именованный кортеж с полями: t1, t2, t3.

Вопрос: что такое тип t3?
Это список диктов или список именованных кортежей (неявно преобразуемых, потому что он определен в TopDefinition как List [NestedTuple]?
Выходные данные предполагают, что это список диктовок, потому что когда я перебираю t3, отображая тип и значение, я вижу:

type=<class 'dict'>,
value={'n1': 'aaa', 'n2': 1}
Is named_tuple=False  

Потом распаковываю {'n1': 'aaa', 'n2': 1} с ** для создания экземпляра NestedDefinition, который работает нормально, так что это должен быть диктат.
С другой стороны, mypy (с параметрами --ignore-missing-import --strict) говорит error: Argument after ** must be a mapping для меня это означает, что это не диктат.

Полный код для запуска ниже:

"""Replicate the problem."""
from typing import Any, List, NamedTuple


class NestedDefinition(NamedTuple):
    """Nested object metadata for mypy type annotation."""

    n1: str
    n2: int


class TopDefinition(NamedTuple):
    """Top object metadata for mypy type annotation."""

    t1: str
    t2: str
    t3: List[NestedDefinition]


def isnamedtupleinstance(x: Any) -> bool:
    """Check if object is named tuple."""
    t = type(x)
    b = t.__bases__
    print("-------{}".format(b))
    if len(b) != 1 or b[0] != tuple:
        return False
    f = getattr(t, '_fields', None)
    if not isinstance(f, tuple):
        return False
    return all(type(n) == str for n in f)


class Nested:
    """Nested object."""

    n1: str
    n2: int

    def __init__(self, nested: NestedDefinition) -> None:
        print("{cName} got:\n\ttype={y}\n\tvalue={v}\n\tIS named_tuple: {b}".format(
            cName=type(self).__name__, y=type(nested), v=nested, b=isnamedtupleinstance(nested)))
        self.n1 = nested.n1
        self.n2 = nested.n2


class Top:
    """Top object."""

    t1: str
    t2: str
    t3: List[Nested]

    def __init__(self, top: TopDefinition) -> None:
        print("{cName} got:\n\ttype={y}\n\tvalue={v}".format(cName=type(self).__name__,
                                                             y=type(top), v=top))

        self.t1 = top.t1
        self.t2 = top.t2
        self.t3 = []
        if top.t3:
            for sub_item in top.t3:
                print("Nested passing:\n\ttype={t},\n\tvalue={v}\n\tIs named_tuple={b}".format(
                    t=type(sub_item), v=sub_item, b=isnamedtupleinstance(sub_item)))
                nested = Nested(nested=NestedDefinition(**sub_item))
                self.addNestedObj(nested)

    def addNestedObj(self, nested: Nested) -> None:
        """Append nested object to array in top object."""
        self.t3.append(nested)


def build_data_structure(someDict: List) -> None:
    """Replicate problem."""
    for item in someDict:
        print("Top passing:\n\ttype ={type}\n\tvalue={value}".format(
            type=type(item), value=item))
        w = Top(top=TopDefinition(**item))


x = [
    {
        't1': 'qwe',
        't2': 'QWE',
        't3': [
            {'n1': 'aaa', 'n2': 1},
            {'n1': 'bb', 'n2': 3}
        ]
    },
    {
        't1': 'asd',
        't2': 'ASD',
        't3': [
            {'n1': 'cc', 'n2': 7},
            {'n1': 'dd', 'n2': 9}
        ]
    }
]


build_data_structure(someDict=x)

1 ответ

Решение

Типовые подсказки есть для статической проверки типов. Они не влияют на поведение во время выполнения.

**mapping синтаксис в вызове расширяет только пары ключ-значение верхнего уровня; это как если бы вы позвонили

TopDefinition(t1='qwe', t2='QWE', t3=[{'n1': 'aaa', 'n2': 1}, {'n1': 'bb', 'n2': 3}])

Вызываемый объект не получает никакой информации об источнике этих ключевых аргументов; namedtuple учебный класс __new__ Метод не заботится и не заботится о том, как были заданы аргументы ключевого слова.

Таким образом, список остается неизменным, он не конвертируется для вас. Вы должны сделать это заранее:

def build_data_structure(someDict: List[Mapping]) -> None:
    for item in someDict:
        print("Top passing:\n\ttype ={type}\n\tvalue={value}".format(
            type=type(item), value=item))

        t3updated = []
        for nested in item['t3']:
            if not isinstance(nested, NestedDefinition):
                nested = NestedDefinition(**nested)
            t3updated.append(nested)
        item['t3'] = t3updated
        w = Top(top=TopDefinition(**item))

Потому что вы использовали **mapping анализаторы статического типа, такие как mypy, не могут определить, что ваш список не соответствует List[NestedDefinition] напечатайте подсказку и не будете предупреждать вас об этом, но если вы использовали полный вызов явно, используя отдельные аргументы, как я делал выше, то вы получите сообщение об ошибке, сообщающее, что вы не используете правильные типы.

В mypy, вы также можете использовать TypedDict определение типа, чтобы документировать, к какому типу отображений передан список build_data_structure() содержит, в этот момент Mypy может сделать вывод, что ваш t3 Значения - это списки словарей, а не списки именованных вами кортежей.

Далее error: Argument after ** must be a mapping ошибка, которая mypy дает вам на основе типа подсказки, которые mypy имеет доступ, а не к информации о времени выполнения. Ваш цикл:

for sub_item in top.t3:

говорит mypy что в правильном коде, sub_item должен быть NestedDefinition объект, потому что t3: List[NestedDefinition] так и говорит. И NestedDefinition объект не является отображением, поэтому sub_item ссылка не может быть использована в **mapping вызов.

Тот факт, что вы пробрались в некоторые фактические отображения через непрозрачный TopDefinition(**item) звонит в build_data_structure() (где те item объекты приходят от неквалифицированного List) ни здесь, ни там; mypy не могу знать, какой тип объекта item и поэтому не может делать никаких утверждений о значениях либо.

Другие вопросы по тегам