在Python/mypy中,NamedTuple和TypedDict的主要区别是什么?

49

在我看来,NamedTupleTypedDict非常相似,Python开发人员本身也认识到了这一点。

关于PEP,我宁愿添加一个关于NamedTuple和TypedDict的通用部分,它们非常相似,后者已经具有结构性。你怎么看? source

但是Guido似乎不太确定。

我不确定NamedTuple和TypedDict是否真的很相似(除了它们都是在静态类型世界中处理过时模式的尝试)。

source

因此,这是我懒惰的尝试,让其他人提出一个简洁的比较,在官方文档似乎缺乏的情况下。


1
你认为namedtupledict看起来相似吗? - Azat Ibrakov
@AzatIbrakov 这是一个很好的观点。要了解NamedTupleTypedDict之间的区别,首先应该理解namedtupledict之间的区别 - Jeyekomon
6个回答

99
Python及其社区正在解决“struct”问题:如何将相关值最好地组合成复合数据对象,以便通过名称逻辑/轻松访问组件。有许多竞争方法:
- `collections.namedtuple`实例 - 字典(具有固定/已知的键集) - 可以访问属性的字典(例如stuf) - attrs库 - PEP 557 dataclasses - 为每个结构类型手工制作的普通老式物体 - 类似于`tuple`和`list`的序列,对于每个位置/插槽都有暗示的含义(陈旧但极其常见) - 等等。
因此,“应该有一种——最好只有一种——明显的方法来做到这一点”的说法就不成立了。
`typing`库和Mypy与Python社区一样,正在努力更有效地定义类型/模式,包括复合对象。您提供的链接中的讨论是这种挣扎和尝试寻找前进方向的一部分。 NamedTuple是一个typing的超类,用于从collections.namedtuple工厂生成的结构化对象;TypedDict是Mypy尝试定义使用固定模式字典时出现的键和相应值的类型的。如果你只考虑“我有一组固定的键,应该映射到一组固定的类型化值”,它们是相似的。但是,它们的实现和约束非常不同。袋子和盒子相似吗?也许。也可能不是。这取决于你的观点和你想如何使用它们。倒杯酒,让讨论开始吧!
顺便说一下,NamedTuple现在是Python的正式部分。
from typing import NamedTuple

class Employee(NamedTuple):
    name: str
    id: int

TypedDict最初是作为Mypy实验性功能的一部分,旨在将类型强制应用于字典的异构、结构化使用。然而,从Python 3.8开始,它已被纳入标准库。

try:
    from typing import TypedDict  # >=3.8
except ImportError:
    from mypy_extensions import TypedDict  # <=3.7

Movie = TypedDict('Movie', {'name': str, 'year': int})

还有一种基于类的类型构造函数可用

class Movie(TypedDict):
    name: str
    year: int

尽管有所不同,{{NamedTuple}}和{{TypedDict}}都锁定要使用的特定键以及与每个键对应的值类型。因此,它们基本上是针对相同的目标:成为复合/结构类型的有用输入机制。Python的标准{{typing.Dict}}更专注于更加同质化、平行的映射,定义键/值类型,而不是键本身。因此,在定义恰好存储在字典中的复合对象方面,它并不是非常有用。
ConnectionOptions = Dict[str, str] 

感谢您的写作。您是否故意选择了 TypedDict 语法?因为还有基于类的语法,使它们在语法上看起来与 NamedTuple 完全相同。 - Christoph
1
我使用了Mypy文档中使用的语法。文档通常是确定规范/首选项的最佳来源。 - Jonathan Eunice
2
更新:TypedDict现在已经成为Python 3.8标准库的一部分了!https://docs.python.org/3/library/typing.html#typing.TypedDict - Kevin Languasco
1
@KevinLanguasco 感谢您的更新。已经进行了修订以适应。 - Jonathan Eunice
7
我认为主要讨论了相似之处,但是没有讨论差异。 - Gonzalo

23

有一些细微的差异。请注意,这些容器并不是永远存在的:

如果可能的话,我会选择NamedTuple,如果我希望值被冻结。否则,我会使用数据类。

from dataclasses import dataclass
from typing import NamedTuple, TypedDict
from enum import Enum


class Gender(Enum):
    MALE = "male"
    FEMALE = "female"


## Class definition: Almost the same
@dataclass
class UserDataC:
    name: str
    gender: Gender


class UserTuple(NamedTuple):
    name: str
    gender: Gender


class UserNDict(TypedDict):
    name: str
    gender: Gender


## Object Creation: Looks the same
anna_datac = UserDataC(name="Anna", gender=Gender.FEMALE)
anna_tuple = UserTuple(name="Anna", gender=Gender.FEMALE)
anna_ndict = UserNDict(name="Anna", gender=Gender.FEMALE)

## Mutable values vs frozen values
anna_datac.gender = Gender.MALE
# anna_tuple.gender = Gender.MALE  # AttributeError: can't set attribute
anna_ndict["gender"] = Gender.MALE
# AttributeError: 'dict' object has no attribute 'gender'
# anna_ndict.gender = Gender.MALE

## New attribute
# Note that you can add new attributes like this.
# Python will not complain. But mypy will.
anna_datac.password = "secret"  # Dataclasses are extensible
# anna_tuple.password = "secret"  # AttributeError - named tuples not
# anna_ndict.password = "secret"  # AttributeError - TypedDict not
anna_ndict["password"] = "secret"

## isinstance
assert isinstance(anna_tuple, tuple)
assert isinstance(anna_ndict, dict)

为什么我更喜欢使用NamedTuple而非namedtuple

我认为它更加直观易懂,同时也让mypy有更多的检查可能性:

class UserTuple(NamedTuple):
    name: str
    gender: Gender

# vs

UserTuple = namedtuple("UserTuple", ["name", "gender"])

为什么我更喜欢元组而不是字典

如果我不需要可变的东西,我更喜欢它们不可变。这样我就可以防止意外的副作用。


10

在Python 3.8+中,TypedDict是一个简单的类型化命名空间,在运行时等效于普通的字典。

NamedTuple是“元组子类”。请注意,

命名元组实例没有每个实例字典,因此它们很轻便,不需要比常规元组更多的内存。

来自这里

命名元组子类也可以有文档字符串和方法。

用我的话来说,NamedTuple更像自定义对象,TypedDict则更像类型化字典。

从这些描述中可以看出,我预期NamedTuple在运行时和内存方面具有一些(小)优势。但是,如果您正在使用期望字典的API,TypedDict可能更可取,因为它是字典(尽管您也可以通过其_asdict()方法从NamedTuple创建字典)。


5

以下内容来自Steven F. Lott和Dusty Phillips的优秀书籍"Python面向对象编程":

  1. 在很多情况下,dataclasses 提供了许多有用的功能,而且写的代码更少。它们可以是不可变的或可变的,给我们提供了广泛的选择。
  2. 对于数据不可变的情况,NamedTuple 比被冻结的 dataclass 稍微高效一些,大约高出5%-不多。在这里平衡的关键是昂贵的属性计算。虽然 NamedTuple 可以具有属性,但如果计算非常昂贵并且结果经常使用,则事先计算它可能有所帮助,这是 NamedTuple 不擅长的。在极少数需要预先计算属性值的情况下,查看 dataclasses 和它们的 __post_init__() 方法的文档作为更好的选择。
  3. 当键的完整集合事先未知时,字典是理想的。当我们开始设计时,我们可能会使用字典进行一次性原型或概念验证。当我们尝试编写单元测试和类型提示时,我们可能需要增加正式性。在某些情况下,可能已知可能键的域,并且 TypedDict 类型提示作为表征有效键和值类型的方法是有意义的。

3
“NamedTuple”是一种特定的类型。顾名思义,它是一个元组,扩展为具有命名条目。
“TypedDict”不是一个真正的对象,您不能(或者至少不应该)使用它,而是用于添加类型信息(对于mypy类型检查器)以注释在字典具有不同类型的各种键的情况下的类型,即实际上应该使用“NamedTuple”的所有地方。它非常有帮助,可以注释现有代码,而无需进行重构。

0
其他可能值得一提的区别: - `NamedTuple` 可以像 `tuple` 一样使用,例如 `fun(*userTuple)`,`a, b = userTuple` - `TypedDict` 可以像 `dict` 一样使用,例如 `fun(**userDict)`
上述行为也与 `dataclass` 不同,`dataclass` 中的字段通常需要显式引用 - 如果不使用 `dataclasses.astuple()`、`.asdict()` 的话。
相似之处:
  • TypedDict 只能继承自其他的 TypedDict [1],而 NamedTuple 只能继承自其他的 NamedTuple
  • 两者都支持 泛型,在最新的 Python 版本中 [2][3]

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