【问题标题】:Why is arithmetic not supported for dict?为什么dict不支持算术?
【发布时间】:2020-01-03 17:52:07
【问题描述】:

在 Python 中,可以对列表和元组求和,例如

>>> print([1, 2] + [4, 5]) 
>>> [1, 2, 4, 5]

>>> print((1, 2) + (4, 5))
>>> (1, 2, 3, 4)

但是尝试对 dicts 做同样的事情会引发:

TypeError: unsupported operand type(s) for +: 'dict' and 'dict'

我想在合并两个具有相同键的字典时,可能会出现与 update() 相同的行为:

>>> foo = {'a': 10, 'b': 20}
>>> bar = {'a': 20}
>>> foo.update(bar)
>>> print(foo)
>>> {'a': 20, 'b': 20}

为什么没有实现这些操作数?有任何优化问题还是设计问题?

【问题讨论】:

  • 当两个 dicts 具有相同的键(具有不同的值)时会发生什么?
  • @UnholySheep 我猜和update() 的行为相同。

标签: python python-internals


【解决方案1】:

2020 年 5 月更新:PEP 584,在此答案 was accepted in February 2020 中进行了讨论,并将成为 Python 3.9 中的一项功能。我认为下面的讨论是我最初答案的一部分,今天仍然很重要,我暂时将其保留为上下文。


原答案:

这已被考虑(见PEP 584)。但是,也存在一些问题。那里考虑了一些非常有趣的观点,绝对值得一读。主要是:如果存在冲突(即我们要添加的字典中重复相同的键)会发生什么?此外,加法运算不是可交换的,而且重复的加法不等于乘法。

有关反对的详细列表,请参阅PEP 584: Major objections

让我们简单回顾一下(下面的讨论可以看作是对 PEP 584 的总结,我仍然建议你去阅读它):

字典加法不可交换

这源于这样一个事实,如果我们试图将两个字典中存在的键相加,那么最好的解决方案可能是选择“获胜方”。我的意思是,如果可以添加 dict,那么下面的代码应该做什么:

dictA = {"key1": 1, "key2": 2}
dictB = {"key1": 3, "key2": 4}

dictC = dictA + dictB
print(dictC) # ???

dictA + dictBdictA.update(dictB) 具有相似的结果是有意义的,其中dictB 将“覆盖”两个字典中重复的键的值。然而,这导致了问题:

我们希望加法运算是可交换的,但dictA + dictBdictB + dictA 不同。

一个反论点是已经存在不可交换的加法运算,例如列表或字符串加法:

listA = [1, 2]
listB = [3, 4]

print(listA + listB) # [1, 2, 3, 4]
print(listB + listA) # [3, 4, 1, 2]

话虽如此,我敢打赌大多数人并不介意,因为将listA + listB 视为列表连接是很自然的,当给出一个表达式时,我们直观地知道会发生什么(字符串添加/连接也是如此) .当然,listB + listA 会返回不同的东西。然而,推断dictA + dictB 会产生什么并不明显(这是主观的,但我认为大多数人都会同意这一点)。

注意there are other possible ways to resolve conflicts,但他们都有自己的问题。

字典加法效率低下

考虑将多个字典加在一起:

dictA + dictB + dictC + dictD ...

字典加法不能很好地扩展,允许这样的表达可能会鼓励不良做法。这又是主观的(就像所有这些反对意见一样),但这似乎是一个普遍的问题。

重复加法应该等价于乘法

我之前提到过。如果允许加法,人们会期望有一个表示重复加法的乘法运算符,类似于列表和字符串可能的情况:

listA = [1, 2]
stringA = 'abc'
dictA = {"key1": 1, "key2": 2}

print( listA*3 ) # [1, 2, 1, 2, 1, 2] -- similar to listA + listA + listA
print( stringA*3) # abcabcabc -- similar to stringA + stringA + stringA
print( dictA*3) # ???

我们将如何以自然的方式处理这个问题?

字典加法是有损的

如果我们以与 dictA.update(dictB) 相同的方式处理冲突,那么这将导致丢失数据的加法操作,而没有其他形式的加法是有损的。

Dict contains tests will fail

我们希望a in a+b 为真,这适用于其他类型,例如字符串和元组:

print(stringA in stringA + stringB) # True

这是有争议的,因为这不适用于其他系列。

只有一种方法 - 不止一种方法

非常主观且值得商榷,但许多人认为,没有一种“自然”的方式来处理冲突这一事实违反了The Zen of Python 中的一项原则:

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

字典加法不像串联

同样,另一个源于字典中添加的反对意见与其他集合(例如列表)中的添加不同。有些人认为这是有问题的:

len(dictA + dictB) == len(dictA) + len(dictB) # False

添加字典使代码更难理解

最后,PEP 584 中列出的最后一个反对意见是我们一次又一次讨论的内容,dictA + dictB 不直观,很难知道那段代码会做什么。

【讨论】:

【解决方案2】:

通常+ 有两个主要用途:

  • 加法
  • 串联

它们都没有明确地适用于字典,因此它不适用于字典。例如,当两个字典都包含相同的键时,连接的含义并不明确。是否应该添加、连接、覆盖这些值?所以没有实现加法遵循"principle of least astonishment"

但是,有一个字典子类实现了+,这意味着按元素添加:collections.Counter

>>> from collections import Counter
>>> Counter('abc') + Counter('ace')
Counter({'a': 2, 'b': 1, 'c': 2, 'e': 1})

【讨论】:

    【解决方案3】:

    在 Python 3.9 中添加了dict union operator,例如:

    >>> d = {'spam': 1, 'eggs': 2, 'cheese': 3}
    >>> e = {'cheese': 'cheddar', 'aardvark': 'Ethel'}
    >>> d | e
    {'spam': 1, 'eggs': 2, 'cheese': 'cheddar', 'aardvark': 'Ethel'}
    >>> e | d
    {'cheese': 3, 'aardvark': 'Ethel', 'spam': 1, 'eggs': 2}
    

    另外,看看motivation,它很好地概述了为什么将它包含在语言中。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-09-26
      • 2021-12-12
      • 1970-01-01
      • 2013-08-06
      • 2011-03-17
      • 2018-07-21
      • 2013-01-04
      相关资源
      最近更新 更多