为什么dict不支持算术?
Why is arithmetic not supported for dict?
在Python中,可以对列表和元组求和,例如
>>> print([1, 2] + [4, 5])
>>> [1, 2, 4, 5]
>>> print((1, 2) + (4, 5))
>>> (1, 2, 3, 4)
但是尝试对听写做同样的事情会引发:
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}
为什么这些操作数没有实现?任何优化问题还是设计使然?
2020 年 5 月更新: PEP 584, which is discussed in this answer, was accepted in February 2020,并将成为 Python 3.9 中的一项功能。我认为接下来的讨论是我最初回答的一部分,今天仍然有意义,我暂时将其保留在上下文中。
原回答:
这已被考虑(参见 PEP 584)。但是,存在一些问题。那里考虑了一些非常有趣的观点,绝对值得一读。主要是:如果有冲突会发生什么(即在我们要添加的字典中重复相同的键)?此外,具有不可交换的加法运算也不是很好,并且重复加法不等同于乘法。
有关反对的详细列表,请参阅 PEP 584: Major objections。
让我们简单回顾一下(以下讨论可以看作是对 PEP 584 的总结,我仍然建议您去阅读):
字典加法不可交换
这是因为如果我们试图加起来的两个字典中存在键,那么最好的解决方案可能是选择 'winning side'。我的意思是,如果可以添加字典,下面的代码应该做什么:
dictA = {"key1": 1, "key2": 2}
dictB = {"key1": 3, "key2": 4}
dictC = dictA + dictB
print(dictC) # ???
dictA + dictB
与 dictA.update(dictB)
有相似的结果是有意义的,其中 dictB
会 'overwrite' 两个字典中重复的键的值.然而,这导致了问题:
我们希望加法运算是可交换的,但 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
视为列表串联是很自然的,当给出一个表达式时,我们凭直觉知道会发生什么(字符串也是如此addition/concatenation)。自然而然地,listB + listA
会 return 有所不同。然而,推断 dictA + dictB
会产生什么并不明显(这是主观的,但我认为大多数人都会同意这一点)。
注意there are other possible ways to resolve conflicts,但他们都有自己的问题。
dict加法会很低效
考虑一起添加多个词典:
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
这有待商榷,因为这不适用于其他集合。
只有一种方法 - 不止一种方法
高度主观且值得商榷,但许多人认为没有 'natural' 处理冲突的方法这一事实违反了 The Zen of Python 中的一项原则:
There should be one-- and preferably only one --obvious way to do it.
字典加法不同于拼接
同样,另一个反对意见源于字典中的加法与其他集合(例如列表)中的加法不同。有些人认为这是有问题的:
len(dictA + dictB) == len(dictA) + len(dictB) # False
字典添加使代码更难理解
最后,PEP 584 中列出的最后一个反对意见是我们反复讨论过的,dictA + dictB
不直观,很难知道那段代码会做什么。
通常+
有两个主要用途:
- 加法
- 串联
None 其中明确适用于字典,因此它没有为字典实现。例如,当两个字典都包含相同的键时,串联意味着什么并不明确。是否应该添加、连接、覆盖这些值?所以不执行加法遵循 "principle of least astonishment".
然而,有一个字典子类实现了 +
,它表示逐元素相加:collections.Counter
:
>>> from collections import Counter
>>> Counter('abc') + Counter('ace')
Counter({'a': 2, 'b': 1, 'c': 2, 'e': 1})
在Python中添加了3.9dict 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,它很好地概述了为什么将其包含在语言中。
在Python中,可以对列表和元组求和,例如
>>> print([1, 2] + [4, 5])
>>> [1, 2, 4, 5]
>>> print((1, 2) + (4, 5))
>>> (1, 2, 3, 4)
但是尝试对听写做同样的事情会引发:
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}
为什么这些操作数没有实现?任何优化问题还是设计使然?
2020 年 5 月更新: PEP 584, which is discussed in this answer, was accepted in February 2020,并将成为 Python 3.9 中的一项功能。我认为接下来的讨论是我最初回答的一部分,今天仍然有意义,我暂时将其保留在上下文中。
原回答:
这已被考虑(参见 PEP 584)。但是,存在一些问题。那里考虑了一些非常有趣的观点,绝对值得一读。主要是:如果有冲突会发生什么(即在我们要添加的字典中重复相同的键)?此外,具有不可交换的加法运算也不是很好,并且重复加法不等同于乘法。
有关反对的详细列表,请参阅 PEP 584: Major objections。
让我们简单回顾一下(以下讨论可以看作是对 PEP 584 的总结,我仍然建议您去阅读):
字典加法不可交换
这是因为如果我们试图加起来的两个字典中存在键,那么最好的解决方案可能是选择 'winning side'。我的意思是,如果可以添加字典,下面的代码应该做什么:
dictA = {"key1": 1, "key2": 2}
dictB = {"key1": 3, "key2": 4}
dictC = dictA + dictB
print(dictC) # ???
dictA + dictB
与 dictA.update(dictB)
有相似的结果是有意义的,其中 dictB
会 'overwrite' 两个字典中重复的键的值.然而,这导致了问题:
我们希望加法运算是可交换的,但 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
视为列表串联是很自然的,当给出一个表达式时,我们凭直觉知道会发生什么(字符串也是如此addition/concatenation)。自然而然地,listB + listA
会 return 有所不同。然而,推断 dictA + dictB
会产生什么并不明显(这是主观的,但我认为大多数人都会同意这一点)。
注意there are other possible ways to resolve conflicts,但他们都有自己的问题。
dict加法会很低效
考虑一起添加多个词典:
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
这有待商榷,因为这不适用于其他集合。
只有一种方法 - 不止一种方法
高度主观且值得商榷,但许多人认为没有 'natural' 处理冲突的方法这一事实违反了 The Zen of Python 中的一项原则:
There should be one-- and preferably only one --obvious way to do it.
字典加法不同于拼接
同样,另一个反对意见源于字典中的加法与其他集合(例如列表)中的加法不同。有些人认为这是有问题的:
len(dictA + dictB) == len(dictA) + len(dictB) # False
字典添加使代码更难理解
最后,PEP 584 中列出的最后一个反对意见是我们反复讨论过的,dictA + dictB
不直观,很难知道那段代码会做什么。
通常+
有两个主要用途:
- 加法
- 串联
None 其中明确适用于字典,因此它没有为字典实现。例如,当两个字典都包含相同的键时,串联意味着什么并不明确。是否应该添加、连接、覆盖这些值?所以不执行加法遵循 "principle of least astonishment".
然而,有一个字典子类实现了 +
,它表示逐元素相加:collections.Counter
:
>>> from collections import Counter
>>> Counter('abc') + Counter('ace')
Counter({'a': 2, 'b': 1, 'c': 2, 'e': 1})
在Python中添加了3.9dict 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,它很好地概述了为什么将其包含在语言中。