【问题标题】:What are the main differences of NamedTuple and TypedDict in Python / mypyPython/mypy中NamedTuple和TypedDict的主要区别是什么
【发布时间】:2019-04-23 20:21:45
【问题描述】:

在我看来,NamedTupleTypedDict 非常相似,Python 开发人员自己也意识到了这一点。

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

但是 Guido 似乎对此不太确定。

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

source

所以,这是我懒惰的尝试,让其他人在官方文档似乎缺乏的地方提出一个清晰的比较。

【问题讨论】:

  • namedtuple & dict 看起来像你吗?

标签: python dictionary types namedtuple mypy


【解决方案1】:

摘自 Steven F. Lott 和 Dusty Phillips 的优秀书籍 "Python Object-Oriented Programming"

  1. 在很多情况下,数据类提供了许多有用的功能,而代码编写更少。它们可以是不可变的,也可以是可变的,从而为我们提供了广泛的选择。
  2. 对于数据不可变的情况,NamedTuple 比冻结数据类的效率略高约 5% - 不多。在这里平衡的是昂贵的属性计算。虽然 NamedTuple 可以具有属性,但如果计算成本非常高且结果被频繁使用,它可以帮助提前计算它,这是 NamedTuple 不擅长的。查看数据类及其 post_init() 方法的文档,作为在极少数情况下提前计算属性值很有帮助的更好选择。
  3. 当事先不知道完整的键集时,字典是理想的选择。当我们开始设计时,我们可能有一次性的原型或使用字典的概念证明。当我们尝试编写单元测试和类型提示时,我们可能需要增加形式。在某些情况下,可能的键的域是已知的,TypedDict 类型提示作为一种表征有效键和值类型的方式是有意义的。

【讨论】:

    【解决方案2】:

    有几个细微差别。请注意,这些容器并非永远存在:

    如果可能的话,如果我想冻结这些值,我会选择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"])
    

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

    如果我不需要事物是可变的,我喜欢它们不可变。这样可以防止意外的副作用

    【讨论】:

    • 你能解释一下你为什么选择NamedTuple吗?
    【解决方案3】:

    TypedDict(在 3.8+ 中)是

    一个简单的类型命名空间。在运行时它相当于一个普通的 dict。

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

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

    和 (from here)

    NamedTuple 子类也可以有文档字符串和方法

    用我自己的话来说,NamedTuple 更像是一个自定义对象,而TypedDict 更像是一个类型化的字典。

    我还没有检查过,但是从这些描述中,我希望NamedTuplesTypedDicts 有一些(小的)运行时和内存优势。

    但是,例如,如果您使用的 API 需要 dict,则 TypedDict 可能更可取,因为它是 dict(尽管您也可以从 @987654334 创建 dict @ 通过其_asdict() 方法)。

    【讨论】:

      【解决方案4】:

      Python 及其社区正在努力解决“结构”问题:如何将相关值最好地分组到允许逻辑/轻松访问组件(通常按名称)的复合数据对象中。有许多种相互竞争的方法:

      • collections.namedtuple 实例
      • 字典(带有一组固定/已知的键)
      • 属性可访问的字典(如stuf
      • attrs
      • PEP 557 dataclasses
      • 为每种结构类型手工制作的普通旧定制对象
      • tuplelist 等序列对每个位置/插槽都有隐含含义(过时但非常常见)

      “应该有一种——最好只有一种——明显的方法。”

      typing 库和 Mypy 与整个 Python 社区一样,都在努力解决如何更有效地定义类型/模式,包括复合对象。您链接到的讨论是摔跤的一部分,并试图找到前进的道路。

      NamedTuple 是由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})
      

      基于类的类型构造函数is also available:

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

      尽管存在差异,NamedTupleTypedDict 都锁定了要使用的特定键,以及每个键对应的值类型。因此,他们的目标基本相同:为复合/结构类型提供有用的类型化机制。

      Python 的标准 typing.Dict 专注于更同质的并行映射,定义键/值类型,而不是键本身。因此,它在定义碰巧存储在字典中的复合对象时不是很有用。

      ConnectionOptions = Dict[str, str] 
      

      【讨论】:

      • 感谢您的撰写。您是否故意使用 TypedDict 语法?因为还有基于类的语法使它们看起来与NamedTuple 完全一样(从语法的角度来看)
      • 我使用了the Mypy documentation使用的语法。文档通常是被认为是规范/首选的最佳来源。
      • 更新:TypedDict 现在是从 Python 3.8 开始的标准库的一部分! docs.python.org/3/library/typing.html#typing.TypedDict
      • @KevinLanguasco 感谢您的更新。修改答案以适应。
      • 我认为主要讨论了相似之处,但没有讨论不同之处。
      【解决方案5】:

      NamedTuple 是一种特定类型。顾名思义,它是一个扩展为具有命名条目的元组。

      TypedDict 不是一个真实的对象,你不能(或至少不应该)使用它,而是用于添加类型信息(用于 mypy 类型检查器)以在字典有各种情况下注释类型具有不同类型的键,即基本上所有应该使用NamedTuple 的地方。注释您不想重构的现有代码非常有帮助。

      【讨论】:

        猜你喜欢
        • 2018-11-18
        • 1970-01-01
        • 2019-10-18
        • 2013-06-17
        • 2012-07-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多