命名元组中的类型提示

241

考虑以下代码片段:

from collections import namedtuple
point = namedtuple("Point", ("x:int", "y:int"))

上面的代码只是为了演示我想要实现的内容。 我想使用类型提示创建 namedtuple

你知道任何优雅的方法来达到预期的结果吗?


6
从Python 3.7开始,你可以使用dataclass选项:https://docs.python.org/3/library/dataclasses.html。 - JohnE
3个回答

323
自Python 3.6开始,推荐使用的有类型的命名元组的语法是
from typing import NamedTuple

class Point(NamedTuple):
    x: int
    y: int = 1  # Set default value

Point(3)  # -> Point(x=3, y=1)

从Python 3.7开始,考虑使用dataclasses
from dataclasses import dataclass

@dataclass
class Point:
    x: int
    y: int = 1  # Set default value

Point(3)  # -> Point(x=3, y=1)

36
@JohnE; OP明确要求使用命名元组。是的,许多命名元组的用例最好使用数据类来处理。但引用优秀的为什么不使用NamedTuples中的话:“如果你想要一个带有名称的元组,请毫不犹豫地选择namedtuple”。 - Wolfgang Kuehn
11
使用dataclasses时,不能像Tuple一样对生成的对象进行解构。 - VARAK
35
一个元组是不可变的,而数据类则不是(默认情况下)。它确实有一个frozen标志,可以接近元组的行为。这只是需要注意的一点。 - shao.lo
4
如果您使用dataclass可行,那么您可以进一步使用pydantic包来以优雅的方式在运行时强制执行类型检查。 - izkeros
6
数据类不支持订阅,也不能像命名元组一样在迭代时解包,因此我认为它们远非完美的替代品。 - Vichoko
显示剩余3条评论

174
你可以使用 typing.NamedTuple
来自文档:

namedtuple类型化版本

>>> import typing
>>> Point = typing.NamedTuple("Point", [('x', int), ('y', int)])

这仅在Python 3.5及以上版本中存在。


我这样声明它:GeoPoint = NamedTuple('GeoPoint', [('longitude', float), ('latitude', float)]),然后我尝试使用 geo = GeoPoint(**data),其中 data 是包含所需键和值为 decimal.Decimal 的字典,并且没有转换为浮点数发生;(没有 TypeError :( :( 那么这个 typing.NamedTuple 是如何工作的呢?请参见 https://gist.github.com/andilabs/15002176b2bda786b9037077fa06cc71。 - andilabs
15
据我所知,@andi打字并不会强制或转换变量的类型。 - Bhargav Rao
13
在更新的版本中,你可以将命名元组声明为Point = typing.NamedTuple("Point", x=int, y=int),这样会更加简洁明了。 - Marked as Duplicate
1
此语法的文档、讨论和示例几乎不存在。我只在 cpython typing.py 文件 中找到了它。它声明此语法可用于 3.6+,至少在 3.7+ 代码中存在。但是,在成员/类型声明旁设置默认值的更清晰版本似乎不存在。 - JWCS
@JWCS 确实如此。这有点可惜。类语法有时候有些过度设计。 - lowercase00

14

公正起见,typing中的NamedTuple

>>> from typing import NamedTuple
>>> class Point(NamedTuple):
...     x: int
...     y: int = 1  # Set default value
...
>>> Point(3)
Point(x=3, y=1)

等同于经典的namedtuple

>>> from collections import namedtuple
>>> p = namedtuple('Point', 'x,y', defaults=(1, ))
>>> p.__annotations__ = {'x': int, 'y': int}
>>> p(3)
Point(x=3, y=1)

所以,“NamedTuple”只是“namedtuple”的语法糖。
下面是从“Python3.10”的源代码中找到的创建“NamedTuple”的函数。我们可以看到,它使用了“collections.namedtuple”构造器,并从提取的类型添加了“__annotations__”。
def _make_nmtuple(name, types, module, defaults = ()):
    fields = [n for n, t in types]
    types = {n: _type_check(t, f"field {n} annotation must be a type")
             for n, t in types}
    nm_tpl = collections.namedtuple(name, fields,
                                    defaults=defaults, module=module)
    nm_tpl.__annotations__ = nm_tpl.__new__.__annotations__ = types
    return nm_tpl

语法糖是解析器可以用更基本的语法替换的东西。NamedTuple比这要复杂一些,它是一个在运行时实际执行某些操作的函数。 - chepner
是的,我知道它在运行时做了什么。它正在提取类型并将它们添加到刚创建的namedtuple__annotations__属性中,使用构造函数collections.namedtuple。我将该代码添加到答案中以便更好地理解。 - mrvol
1
@mrvol 謝謝你。這對我真的很有幫助,讓我明白了實際上發生了什麼,以及兩種情況下的結果對象是相同的。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接