1

我有两个类:TopNested,要创建它们,我需要提供TopDefinitionNestedDefinition对象,它们是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 类型是什么?
它是字典列表还是命名元组列表(隐式转换,因为它在TopDefinition中定义为List[NestedTuple]
输出表明这是字典列表,因为当我遍历 t3 时,显示类型和价值,我看到:

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

然后我{'n1': 'aaa', 'n2': 1}用 ** 解包以创建工作正常的NestedDefinition实例,所以它应该是一个字典。
另一方面,mypy(带有选项--ignore-missing-imports --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)
4

1 回答 1

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的类型,因此也无法对这些值做出任何断言。

于 2018-10-05T14:26:55.403 回答