使用双星运算符从字典中命名的元组:嵌套字段也被解包了吗?
named tuple from dictionary using double-star-operator: are nested fields unpacked too?
我有两个 classes:Top 和 Nested,要创建它们我需要提供 TopDefinition 和 NestedDefinition 对象,属于 NamedTuple 类型(类型注释需要定义)。 Class Top 包含属性,它是 Nested 实例对象的列表。
有一个嵌套的字典,用于创建命名元组的实例。
输入字典 item
如下所示:
type =<class 'dict'>
value={'t1': 'qwe', 't2': 'QWE', 't3': [{'n1': 'aaa', 'n2': 1}, {'n1': 'bb', 'n2': 3}]}
然后用代码
解压创建 class TopDefinition 的实例
q = Top(top=TopDefinition(**item))
用作创建 class Top 实例的输入。这很好用,稍后我可以在 q class 中看到输入参数的类型和值:
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)
类型提示用于静态类型检查。它们不会影响运行时行为。
调用中的**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
type definition 来记录传递给 build_data_structure()
的列表包含什么类型的映射,此时 mypy 可以推断出您的 t3
值是字典列表,而不是命名元组列表。
接下来,mypy
给你的 error: Argument after ** must be a mapping
错误是基于 mypy
可以访问的 类型提示 ,而不是关于运行时信息。你的循环:
for sub_item in top.t3:
告诉mypy
在正确的代码中,sub_item
必须是一个NestedDefinition
对象,因为 t3: List[NestedDefinition]
是这样告诉它的。 NestedDefinition
对象不是映射,因此 sub_item
引用不能用于 **mapping
调用。
您通过 build_data_structure()
中的不透明 TopDefinition(**item)
调用(其中那些 item
对象来自不合格的 List
)偷偷进入了一些实际映射的事实既不是在这里或那里; mypy
不知道 item
是什么类型的对象,因此也无法对这些值做出任何断言。
我有两个 classes:Top 和 Nested,要创建它们我需要提供 TopDefinition 和 NestedDefinition 对象,属于 NamedTuple 类型(类型注释需要定义)。 Class Top 包含属性,它是 Nested 实例对象的列表。
有一个嵌套的字典,用于创建命名元组的实例。
输入字典 item
如下所示:
type =<class 'dict'>
value={'t1': 'qwe', 't2': 'QWE', 't3': [{'n1': 'aaa', 'n2': 1}, {'n1': 'bb', 'n2': 3}]}
然后用代码
解压创建 class TopDefinition 的实例q = Top(top=TopDefinition(**item))
用作创建 class Top 实例的输入。这很好用,稍后我可以在 q class 中看到输入参数的类型和值:
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)
类型提示用于静态类型检查。它们不会影响运行时行为。
调用中的**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
type definition 来记录传递给 build_data_structure()
的列表包含什么类型的映射,此时 mypy 可以推断出您的 t3
值是字典列表,而不是命名元组列表。
接下来,mypy
给你的 error: Argument after ** must be a mapping
错误是基于 mypy
可以访问的 类型提示 ,而不是关于运行时信息。你的循环:
for sub_item in top.t3:
告诉mypy
在正确的代码中,sub_item
必须是一个NestedDefinition
对象,因为 t3: List[NestedDefinition]
是这样告诉它的。 NestedDefinition
对象不是映射,因此 sub_item
引用不能用于 **mapping
调用。
您通过 build_data_structure()
中的不透明 TopDefinition(**item)
调用(其中那些 item
对象来自不合格的 List
)偷偷进入了一些实际映射的事实既不是在这里或那里; mypy
不知道 item
是什么类型的对象,因此也无法对这些值做出任何断言。