Engineering #12: copying dicts and method chaining
An exercise from Launch School:
Write some code to create a deep copy of the dict1 object and assign it to dict2. You should only modify the code where indicated.
# You may modify this line
dict1 = {
'a': [[7, 1], ['aa', 'aaa']],
'b': ([3, 2], ['bb', 'bbb']),
}
dict2 = ??? # You may modify the `???` part
# of this line
# All of these should print False
print(dict1 is dict2)
print(dict1['a'] is dict2['a'])
print(dict1['a'][0] is dict2['a'][0])
print(dict1['a'][1] is dict2['a'][1])
print(dict1['b'] is dict2['b'])
print(dict1['b'][0] is dict2['b'][0])
print(dict1['b'][1] is dict2['b'][1])
The solution is easy — it is just to import the copy module at the top of our file, and then use the deepcopy function:
import copy
dict1 = {
'a': [[7, 1], ['aa', 'aaa']],
'b': ([3, 2], ['bb', 'bbb']),
}
dict2 = copy.deepcopy(dict1)
The hard part is to understand which elements in the two dicts remain the same, and which become different. First of all, what is what?
'a' is the first key in the dict. Its value is a list of two lists:
[[7, 1], ['aa', 'aaa']]
'b' is the second key in the dict. Its value is a tuple of two lists:
([3, 2], ['bb', 'bbb'])
After the deep copy, when we ask Python whether the two dicts, or the values to ‘a’ or ‘b’, or each of the four inner lists are the same object, we learn that it is not. But:
# All of these should print True
print(dict1['a'][0][0] is dict2['a'][0][0])
print(dict1['a'][0][1] is dict2['a'][0][1])
print(dict1['a'][1][0] is dict2['a'][1][0])
print(dict1['a'][1][1] is dict2['a'][1][1])
print(dict1['b'][0][0] is dict2['b'][0][0])
print(dict1['b'][0][1] is dict2['b'][0][1])
print(dict1['b'][1][0] is dict2['b'][1][0])
print(dict1['b'][1][1] is dict2['b'][1][1])
We learn that each of the elements inside the four inner lists of each dict are the same. (dict1['a'][0][0], for example, is the integer 7.) How come?
We don't check the immutable contents of the nested objects inside each list value in the dictionaries. Since they are all immutable, they weren't duplicated.
All the elements are either integers or strings, both of which are immutable. Python stores immutable objects in the same location in memory, and has no need to copy them. So — inside a dict that is a separate copy, inside lists that are separate copies, the elements are the same objects.
What if you do a shallow copy, instead?
dict2 = copy.copy(dict1) # or dict2 = dict(dict1)
print(dict1 is dict2) # False
print(dict1['a'] is dict2['a']) # True
print(dict1['a'][0] is dict2['a'][0]) # True
print(dict1['a'][1] is dict2['a'][1]) # True
print(dict1['b'] is dict2['b']) # True
print(dict1['b'][0] is dict2['b'][0]) # True
print(dict1['b'][1] is dict2['b'][1]) # True
The two dicts are still separate objects, because one is a copy of another. But when we compare any more granularly, we learn that every component of the two dict objects is the exact same.
And a little on method chaining in Python. Example:
tv_show = "Late Night with Conan O'Brien"
tv_show = tv_show.upper().split()
# we're capitalizing with .upper, and splitting into separate strings with .split
print(tv_show)
['LATE', 'NIGHT', 'WITH', 'CONAN', "O'BRIEN"]
Chaining only works when each method in the chain except the last returns an object with at least one useful method. (It doesn't matter what the last method returns.)
This is rare in Python compared to most languages; many methods simply return None. Thus, chaining in Python isn't very common. However, you will encounter it. If you use chaining, keep things simple. A long chain of different kinds of operations can be challenging to understand.
Which kinds of methods can be chained, then? ChatGPT says:
Methods that mutate tend to return None
Methods that create new objects often return them
→ so you can chain when you’re transforming immutable types
→ not when you’re mutating mutable oneschaining is safe for:
str, tuple, bytes, int, float
but dangerous for:
list, dict, set
tl;dr: chaining in python is rare bc most mutating methods return None.
Thanks to Daniel for checking this last part.