为什么字典不支持算术运算?不支持的操作数类型:’dict’ 和 ‘dict’。

huangapple go评论73阅读模式
英文:

Why is arithmetic not supported for dict? Usupported operand type(s) for +: 'dict' and 'dict'

问题

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

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

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

但尝试对字典进行相同操作会引发错误:

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}

为什么没有实现这些操作呢?是由于优化问题还是设计问题呢?

英文:

In Python, there is a possibility to sum lists and tuples, e.g.

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

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

But trying to do the same with dicts will raise:

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

I guess there could be the same behavior as update() has for cases when merging two dicts with the same key:

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

Why these operands aren't implemented? Any optimization issues or just by design?

答案1

得分: 4

2020年5月更新: PEP 584,在此答案中讨论的内容,于2020年2月被接受,将成为Python 3.9的一个特性。我认为接下来的讨论,这是我原始答案的一部分,仍然与今天相关,我将它保留为了解背景的上下文。


原始答案:

这个问题已经被考虑过(请参见PEP 584)。然而,存在一些问题。在那里考虑了一些非常有趣的观点,值得一读。主要问题是:如果存在冲突(即在我们想要添加的字典中重复相同的键),会发生什么情况?此外,具有不可交换性的加法操作并不理想,并且重复的加法不等同于乘法。

有关详细的反对意见,请参见PEP 584:主要反对意见

让我们简要讨论一下(以下讨论可以看作是PEP 584的摘要,我仍然建议您去阅读它):

字典加法不可交换

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

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

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

dictA + dictB 应该有一个类似于 dictA.update(dictB) 的结果,其中 dictB 将“覆盖”那些两个字典中重复的键的值。然而,这会导致一个问题:

我们期望加法操作是可交换的,但 dictA + dictB 会与 dictB + 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 会产生什么结果(这是主观的,但我认为大多数人会同意这一点)。

请注意,还有其他可能的解决冲突的方式,但它们都有自己的问题。

字典加法效率低下

考虑将许多字典相加:

dictA + dictB + dictC + dictD ...

字典加法不会很好地扩展,并且允许这样的表达式可能会鼓励不良实践。这再次是主观的(就像所有这些反对意见一样),但这似乎是一个普遍的担忧。

重复的加法应该等同于乘法

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

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

print( listA*3 ) # [1, 2, 1, 2, 1, 2] -- 类似于 listA + listA + listA
print( stringA*3) # abcabcabc -- 类似于 stringA + stringA + stringA
print( dictA*3) # ???

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

字典加法会丢失数据

如果我们像 dictA.update(dictB) 那样处理冲突,那将导致一个加法操作会丢失数据,而其他形式的加法则不会。

字典包含测试将失败

我们期望 a in a+b 为真,这对于其他类型如字符串和元组也成立:

print(stringA in stringA + stringB) # True

这是有争议的,因为同样的情况不适用于其他集合。

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

这是高度主观和有争议的,但许多人认为,没有一种“自然”的方法来处理冲突违反了Python之禅中的一项原则:

> 应该有一种-- 最好只有一种--明显的方法来做。

字典加法不像连接

再次,这是另一个反对意见,源于字典中的加法与列表等其他集合中的加法不同。一些人认为这是有问题的:

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

字典加法使代码难以理解

最后,在PEP 584中列出的最后一个反对意见是我们一次又一次地讨论的,dictA + dictB 不直观,很

英文:

May 2020 update: PEP 584, which is discussed in this answer, was accepted in February 2020, and will become a feature in Python 3.9. I think the discussion that follows, which is part of my original answer, is still relevant today, and I'm leaving it as is for context for now.


Original answer:

This has been considered (see PEP 584). However, there are some issues. Some very interesting points are considered there, it is definitely worth a read. Mainly: what happens if there are conflicts (i.e. the same key is repeated in the dictionaries that we want to add)? Also, it isn't great to have an addition operation that isn't commutative, and where repeated additions aren't equivalent to multiplications.

For a detailed list of objections, see PEP 584: Major objections.

Let's briefly go over them (the following discussion can be seen as a summary of PEP 584, I still recommend you go and read it):

Dict addition is not commutative

This stems from the fact that if there are keys present in the two dicts that we are trying to add up, then the best solution would probably to choose a 'winning side'. By this I mean, if dict addition was possible, what should the following code do:

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

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

It would make sense for dictA + dictB to have a similar result to dictA.update(dictB), where dictB would 'overwrite' the values for the keys that are repeated in both dictionaries. However that leads to the problem:

We would expect an addition operation to be commutative, but dictA + dictB would be different than dictB + dictA.

A counter argument is that there are already addition operations that are not commutative, such as list or string addition:

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

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

That being said, I bet most people don't mind that since it's natural to think of listA + listB as list concatenation, when given an expression, we intuitively know what to expect (the same goes for string addition/concatenation). And naturally it follows that listB + listA would return something different. However, it's not obvious to deduce what dictA + dictB would yield (this is subjective but I think most people would agree on this).

Note that there are other possible ways to resolve conflicts, but they all have their own problems.

Dict addition will be inefficient

Consider adding many dictionary together:

dictA + dictB + dictC + dictD ...

Dictionary addition wouldn't scale well, and allowing expressions like that to be possible would encourage bad practices. This is again subjective (as are all of these objections), but this does seem to be a common concern.

Repeated addition should be equivalent to multiplication

I mentioned this before. If addition was allowed, one would expect to have a multiplication operator that represented repeated addition, similar to what is possible with lists and strings:

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) # ???

How would we handle this in a natural way?

Dict addition is lossy

If we handled conflicts the same way dictA.update(dictB) does, then that would lead to an addition operation that loses data, where no other form of addition is lossy.

Dict contains tests will fail

We would expect a in a+b to be true, which holds for other types such as strings and tuples:

print(stringA in stringA + stringB) # True

This is up for debate, since the same doesn't apply to other collections.

Only One Way To Do It - More Than One Way To Do It

Highly subjective and highly debatable, but many people argue that the fact that there isn't a 'natural' way to handle conflicts violates one of the principles in The Zen of Python:

> There should be one-- and preferably only one --obvious way to do it.

Dict addition is not like concatenation

Again, another objection that stems from that addition in dictionaries would work differently than addition in other collections such as lists. Some people see this as problematic:

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

Dict addition makes code harder to understand

Finally, the last objection listed in PEP 584 is what we've discussed time and time again, dictA + dictB is not intuitive, and it's hard to know what that piece of code would do.

答案2

得分: 1

Typically + has two main usages:

  • Addition
  • Concatenation

None of them applies unambiguously to dictionaries, so it's not implemented for dicts. For example it's not unambiguous what concatenation means when there both dictionaries contain the same key. Should the values be added, concatenated, overwritten? So not implementing addition follows the "principle of least astonishment."

However there's one dictionary-subclass that implements +, where it means element-wise addition: collections.Counter:

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

Typically + has two main usages:

  • Addition
  • Concatenation

None of them applies unambiguously to dictionaries, so it's not implemented for dicts. For example it's not unambiguous what concatenation means when there both dictionaries contain the same key. Should the values be added, concatenated, overwritten? So not implementing addition follows the "principle of least astonishment".

However there's one dictionary-subclass that implements +, where it means element-wise addition: collections.Counter:

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

答案3

得分: 0

在Python 3.9中新增了字典联合运算符,例如:

>>> 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}

另外,请查看动机,它对为何将此功能包含在语言中进行了良好的概述。

英文:

In Python 3.9 was added dict union operator, for example:

>>> 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}

Also, take a look at motivation, it has a good overview of why this was included in the language.

huangapple
  • 本文由 发表于 2020年1月4日 01:52:07
  • 转载请务必保留本文链接:https://go.coder-hub.com/59583116.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定