Simpler Updating of Dictionaries
Here are additional resources about PEP 584 and dictionaries:
00:00
In the previous episode, I introduced you to the zoneinfo
library. In this episode, I’m going to be talking about the new operators for dictionaries.
00:09 Python 3.9 introduces two new operators for dictionaries. The first is the pipe operator, allowing you to create a new dictionary based on the merging of two existing dictionaries.
00:22
The second is the pipe equals operator that acts like the .update()
function call. Let me start by showing you the old way of doing things. In order to do that, I’m going to need a couple of dictionaries. This first one is PyCon events, and the second one is EuroPython events.
00:46
You can create new dictionaries using the double asterisks operator (**
). This example creates a third dictionary called merged
, which constitutes pycon
and europython
. pycon
is untouched, europython
is untouched, and merged
is the mix of the two of them.
01:09
Another way of doing this is to start with the pycon
dictionary and copy it. merged
is now an exact copy of pycon
, and then iterate through europython
, adding it to merged
.
01:33
This results in the same thing. You could also have achieved this with the .update()
function. But keep in mind, .update()
operates on the existing dictionary, so now pycon
has both the original and the europython
entries inside of it.
01:51
There’s yet another way of doing that, so let me reset pycon
.
02:00
Combining the ideas from before, I can use the .copy()
and .update()
methods together.
02:13
Except—that doesn’t work. And the reason that doesn’t work is because the .update()
function operates in place—it does not return a dictionary.
02:21
So pycon.copy()
gets run, the result of that dictionary has .update()
called upon it, and then .update()
returns nothing. So this short form doesn’t work.
02:32
You can tackle this problem using the recently introduced walrus operator (:=
).
02:43
Pay close attention to where the parentheses are in this example. The first set of parentheses surround the walrus operator. (merged := pycon.copy())
calls pycon.copy()
, returns the result into merged
, and then the walrus operator returns the value of merged
into the expression.
03:02
So the inside of the parentheses is the value of merged
after the .copy()
. .update()
is then run on that value. You can see the result here. Just because you’ve got a result doesn’t mean it was pretty getting there. Python 3.9 has introduced the pipe operator to make cleaner-looking code to solve the same kind of problem.
03:25
First up, merge pycon
and europython
. This is returning a new dictionary, not touching pycon
or europython
. There’s pycon
. See? It’s been untouched.
03:38
So, the pipe operator acts as a merge. The pipe equals operator acts as an update. This time, pycon
has been updated to include both the values it had before, as well as the values from europython
.
03:55 These operators have also been implemented on dictionary-like objects. Let me show you an example.
04:06
This is the defaultdict
. A defaultdict
is just like a dictionary, but instead of raising a KeyError
when something isn’t found, you can set a value to be returned instead.
04:18 Let me just set up a couple of examples.
04:30
The lambda
here tells the defaultdict
that if there is a KeyError
, to return the result of the lambda—in this case, just an empty string.
04:39 Let me set up another one so I can do some merging.
04:48
Now, if I use the double asterisk operator (**
) from before, it’ll work, but it won’t still be a defaultdict
. The result is a regular dictionary. Fortunately, the pipe operator is aware of the class.
05:05
Unlike the other mechanism, with the double asterisk operator, the pipe operator actually returns a defaultdict
with the merged results. The operators are also aware of other ways of constructing dictionaries.
05:19 Let me just build out another example for you, listing off some modules inside of Python.
05:34 And now I can or-equals the kinds of things that you put in a dictionary constructor, like a list of tuples. The end result is an updated dictionary. This gives you power as to how you can update your dictionary, and still do it with clean-looking code.
05:53 The order of operation on the pipe operator is important. Here’s a new example with some places.
06:07
Notice that both of these dictionaries have a key called "Georgia"
. If I merge asia
with usa
first, the dictionary in the second part, i.e. the usa
in this case, overwrites the keys in the first part.
06:23
So "Georgia"
in the merged dictionary is "Atlanta"
. If I do this the other way around, this time, asia
overwrites the usa
.
06:33 The order of operations on your operator changes the resulting values. The pipe and pipe equal operators have been added to most of the dictionary-like objects.
06:46
The only exception is they haven’t been added to the abstract base classes. Like many operators in Python, these have been implemented using dunder methods: .__or__()
and .__ior()__
, for pipe and pipe equals, respectively. Because these are implemented as functions, you can override them and change the behavior yourself.
07:08 For more information on these operators, you can see the original PEP. Next up, I’ll show you the changes to the decorator syntax.
Christopher Trudeau RP Team on Oct. 11, 2020
Yes, the value on the left of the pipe operator is what is merged into:
>>> from collections import defaultdict
>>> cats = defaultdict(lambda:"meow")
>>> cats["small"]
'meow'
>>> dogs = defaultdict(lambda:"woof")
>>> dogs["large"]
'woof'
>>> cats
defaultdict(<function <lambda> at 0x7fbf3da27af0>, {'small': 'meow'})
>>> dogs
defaultdict(<function <lambda> at 0x7fbf3da480d0>, {'large': 'woof'})
>>> cats | dogs
defaultdict(<function <lambda> at 0x7fbf3da27af0>, {'small': 'meow', 'large': 'woof'})
>>> mixed = cats | dogs
>>> mixed["parrot"]
'meow'
You can see the default value of “meow” in the mixed defaultdict, or you can also see the address of the original lambda associated when you print the defaultdict itself out: <function <lambda> at 0x7fbf3da27af0, which appears in both cats
and mixed
.
Become a Member to join the conversation.
rivet on Oct. 10, 2020
What happens if the default value of each
defaultdict
is not the same when you use the merge operator? Does the first default value take precedence?