Именованный кортеж из словаря с использованием оператора двойной звезды: также распакованы ли вложенные поля?
У меня есть два класса: 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
и поэтому не может делать никаких утверждений о значениях либо.