【问题标题】:When dictionary keys are identical, why does Python keep only the last key-value pair?当字典键相同时,为什么 Python 只保留最后一个键值对?
【发布时间】:2021-10-22 20:09:19
【问题描述】:

假设我创建了一个字典a_dictionary,其中两个键值对具有相同的键:

In [1]: a_dictionary = {'key': 5, 'another_key': 10, 'key': 50} 

In [2]: a_dictionary
Out[2]: {'key': 50, 'another_key': 10}

为什么 Python 选择在这里保留最后一个键值对,而不是抛出关于使用相同键的错误(或至少引发警告)?

在我看来,这里的主要缺点是您可能会在不知情的情况下丢失数据。

(如果相关,我在 Python 3.6.4 上运行了上面的代码。)

【问题讨论】:

  • Hm... 如果set 应用于具有重复项的列表,Python 是否也应该引发异常?这似乎很相似。在您的情况下,我会说 IDE 可能会指出这一点,而不是 Python 本身。
  • 我不确定我是否关注@tobias_k。当您在包含重复项的列表上运行set() 时,您期望 set() 函数来删除重复项,而在这里您不需要。另外值得一提的是,这里我们没有“纯”重复项,因为字典值不同。
  • 但是你什么时候期待它,什么时候不期待它?例如,使用 dict 理解来获取基于某个列表的唯一值可能是完全合理的。当然,在这种完全不变的 dict 理解中,这显然是无意的,但我认为这是 IDE 的工作(或者可能是一些 linter)
  • @Alex 你应该期待它。正如您对套装的期望一样。在 3.6 版之前,您无法安全地判断哪个键:值对将保留。从 3.6 开始,它是最后插入的。
  • 其实,通过快速搜索,它已经讨论过on b.p.o.。然而,拒绝很大程度上归结为“我们不会在没有讨论 -ideas 和 -dev 的情况下做出这样的重大改变”,所以你仍然必须去搜索那些邮件列表,看看是否发生过这样的讨论。 (如果确实如此,则永远不会更新该错误以提及它,但有时会发生这种情况。)至少该错误缩小了要搜索的日期范围

标签: python python-3.x dictionary


【解决方案1】:

如果你的问题是为什么 Python dict 显示最初是这样设计的……可能没人知道。


我们知道何时做出决定。 Python 0.9.x (1991-1993) 没有 dict 显示; Python 1.0.x (1994) 做到了。他们的工作方式与今天完全相同。来自文档:1

字典显示会产生一个新的字典对象。

键/数据对从左到右进行评估以定义 字典条目:每个键对象都用作进入字典的键 字典来存储相应的数据。

对键值类型的限制已在前面列出 部分类型。

未检测到重复键之间的冲突;最后 为给定键存储的数据(显示中最右边的文本) 价值占上风。

并且,测试它:

$ ./python
Python 1.0.1 (Aug 21 2018)
Copyright 1991-1994 Stichting Mathematisch Centrum, Amsterdam
>>> {'key': 1, 'other': 2, 'key': 3}
{'other': 2, 'key': 3}

但没有提到 Guido 选择这种设计的原因:

此外,如果您查看具有相似功能的不同语言,其中一些保留最后一个键值对,如 Python,一些保留任意键值对,一些引发某种错误……每种语言都足够了你不能说这是一个显而易见的设计,这就是 Guido 选择它的原因。


如果你想要一个可能并不比你自己猜测的更好的疯狂猜测,这里是我的:

编译器不仅可以,而且确实,通过创建一个空的dict并在其中插入键值对来有效地从文字中构造const值。因此,默认情况下,您会获得允许重复、最后一个键获胜的语义;如果您想要其他任何东西,则必须编写额外的代码。而且,没有令人信服的理由来选择一个而不是另一个,Guido 选择不编写额外的代码。


那么,如果 的设计没有令人信服的理由,为什么 24 年来没有人试图改变它?

好吧,有人提出了功能请求 (b.p.o. #16385),以使重复键在 3.4 中成为错误。 但是当有人建议它在-ideas 上提出时显然消失了。)它可能已经出现了几次,但显然没有人希望它改变得足以推动它。

同时,他最接近 Python 现有行为的实际论点是 Terry J. Reedy 的评论:

如果没有更多用例和支持(来自关于 python-ideas 的讨论),我认为这应该被拒绝。能够重写键是 Python 字典的基础,以及为什么它们可以用于 Python 的可变命名空间。一个 write-once 或 write-key-once dict 会是别的东西。

至于文字,代码生成器可能依赖于能够编写重复的键,而不必返回并删除以前的输出。


1。我不认为 1.0 的文档可以在任何地方直接链接,但您可以下载 the whole 1.0.1 source archive 并从 TeX 源构建文档。

【讨论】:

    【解决方案2】:

    我认为@tobias_k 有最终答案——否则就会出现不一致。如果

    {'key': 0, 'key': 1}
    

    抛出一个错误,然后我会想到

    lst = [('key', 0), ('key', 1)]
    dict(lst)
    

    失败,然后我会期待

    d = {}
    d['key'] = 0
    d['key'] = 1
    

    也。但是,当然,最后一个选项显然不是我想要的,所以回到链条上,我们达到了当前的行为。

    【讨论】:

      【解决方案3】:

      从概念上讲,您可以将字典创建视为一个迭代的增量过程。换句话说,字典字面量的赋值:

      a_dictionary = {'key': 5, 'another_key': 10, 'key': 50}
      

      相当于一系列单一的赋值语句:

      a_dictionary['key'] = 5
      a_dictionary['another_key'] = 10
      a_dictionary['key'] = 50
      

      当然,如果一个键多次出现,重新分配一个新值并没有错。

      【讨论】:

        【解决方案4】:

        通常您希望覆盖该值而不是引发错误。 如果你想要一个字典来保护自己不被覆盖值,那么创建一个新的类来包装 Dictionary 类并在任何值被覆盖时抛出错误。

        【讨论】:

        • 这对像这样的 dict 显示有什么帮助?即使您编写了MyDicf({'key': 1, 'key': 2}),当您的MyDlct 构造函数开始运行时,它也会收到一个带有一个键值对的字典。除非你想编写一个导入钩子,用不同的东西替换源中的 dict 显示,否则这不会做任何事情。
        • @abarnert 不正确。您可以让构造函数处理这样的情况。不知道你在说什么关于编写源代码的钩子,因为这实际上是不需要的。解决方案是一个简单的包装类
        • 向我展示包装类如何处理 OP 的 a_dictionary = {'key': 5, 'another_key': 10, 'key': 50},甚至是 a_dictionary = MyDic({'key': 5, 'another_key': 10, 'key': 50})
        猜你喜欢
        • 2021-01-19
        • 2021-06-28
        • 1970-01-01
        • 2014-01-13
        • 2018-03-15
        • 1970-01-01
        • 2013-08-31
        相关资源
        最近更新 更多