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

Question:

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?

Asked By: funnydman

||

Answers:

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.

Answered By: Ismael Padilla

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})
Answered By: MSeifert

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.

Answered By: funnydman