именованный кортеж из словаря с использованием оператора двойной звезды: также распакованы ли вложенные поля? - PullRequest
0 голосов
/ 05 октября 2018

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

Существует вложенный 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?
Это список dicts или список именованных кортежей (неявно преобразованный, потому что он определен в TopDefinition as List [NestedTuple] ?
Вывод предполагает, что это список dicts, потому что когда я перебираю t3, отображая тип и значение, я вижу:

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

Затем я распаковываю {'n1': 'aaa', 'n2': 1} с помощью **, чтобы создать NestedDefinition экземпляр, который работает нормально, так что это должен быть dict.
С другой стороны, 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 Ответ

0 голосов
/ 05 октября 2018

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

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

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

Вызванному объекту не дали никакой информации об источнике этих ключевых аргументов;метод namedtuple class __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, и поэтому не может делать никаких утверждений относительно значений.

...