Demystifying Python Dictionaries
If You ever had some sort of task in Python which required involvement of dictionaries, then You have probably found a lot of information on Google. I know that, because I have also been at the same position as You. The problem is that You can rarely find detailed explanations why You should work with dictionaries in one way or another, so I have decided to put all the pieces in one place here and show what options You can have when working with dictionaries in Python and how to understand them better.
What is dictionary in Python?
To put it straight and simple, dictionary is a data structure which consists of key: value pairs with the requirement, that every key in the dictionary must be unique. It is not possible to have two different entries in the dictionary with the same key. These key: value pairs have to be comma separated. You have to use braces {}. So, for example, to create Your first empty dictionary you can write:
my_first_dict = {}
print(my_first_dict)
print(type(my_first_dict))
Output:
{}
<class 'dict'>
As You can see, if we print out the type of our dictionary, You will see that this object created with empty brackets {} is of dict class. This means that we will have a lot of methods which we will be able to use with our dictionaries, but more on that later.
Anyways, You can also create a dictionary using dict constructor:
my_second_dict = dict()
print(my_second_dict)
print(type(my_second_dict))
Output:
{}
<class 'dict'>
As you can see, the output is the same as using brackets {} for creating a dictionary. From functionality point of view, there is none. Both dictionaries will behave the same, but brackets {} approach is faster. The questions is why? To answer that, You need to get some help from the dis module. It is a disassembler for Python byte code. Official documentation can be found on official below:
Now I will not go deeper into dis module because that is worth to discuss in a separate article. For now, it will be enough for us to now that dis module has a an analysis function which is also called dis, so to use it we can de the following:
from dis import dis
Now we can use this dis function and pass a string of source code which will be decompiled. So let’s use this with both {} and dist(). I added one empty print statement just for better visualization.
dis("{}")
print("")
dis("dict()")
Output:
1 0 BUILD_MAP 0
2 RETURN_VALUE1 0 LOAD_NAME 0 (dict)
2 CALL_FUNCTION 0
4 RETURN_VALUE
Now what do we see above? Let’s start with the left number which is 1 in both cases. This is the line number of analyzed code. Since our code here had only one line, it is showing 1. If You would decompile a function or something that has more lines, You would see more numbers and instructions generated for each line.
Now as you can see, calling {} in python generates only two instructions: BUILD_MAP and RETURN_VALUE. What BUILD_MAP instruction does it basically will build a dictionary object from elements stored in stack. Then RETURN_VALUE will simply return result to the caller of the function. So it is just two instructions. Now, in the case of using dict() we can see that first instruction is LOAD_NAME. This instruction will push parameters passed to dict() constructor onto the stack. Next, CALL_FUNCTION instruction will pop all values that were pushed to the stack, then it will use these values as arguments and will call the callable object. The result will be pushed back to the stack. Note that this instruction since Python 3.6 version is used only generated for calls with positional arguments. The last instruction is RETURN_VALUE, which we have already discussed. So, as You can see, creating a dictionary by using dict() constructor will be decompiled intro three instructions, and {} will be decompiled into two.
Now, You can try this with by passing one key:value pair using same methods:
dis("{\"One\":1}")
print("")
dis("dict(One=1)")
print("")
Output:
1 0 LOAD_CONST 0 ('One')
2 LOAD_CONST 1 (1)
4 BUILD_MAP 1
6 RETURN_VALUE1 0 LOAD_NAME 0 (dict)
2 LOAD_CONST 0 (1)
4 LOAD_CONST 1 (('One',))
6 CALL_FUNCTION_KW 1
8 RETURN_VALUE
Now as You probably see, almost all instructions are the same, except that LOAD_CONST instruction is generated to push parameters to the stack. Also, CALL_FUNCTION_KW is generated instead of CALL_FUNCTION. This instruction is generated when keyword arguments are passed to the object as instead of positional arguments. Anyways, You can see that creating a dictionary with {} syntax is always decompiled to one instruction less than using dict().
Now I am not sure if it is even possible to measure the execution speed of separate instructions in python (if You know how to do it, please share it in the comments), but for sure we can measure execution time of various functions or a block of source code using a timeit module in python. I will also not dive deeper into this module because it is not the scope of this article, but I can say that this module has timeit function, to which You can pass source code to be measured and a counter, which will show how many times the code snippet will be executed. So first, lets import timeit function from timeit module
from timeit import timeit
Then, let’s create a dictionary using {} and dict() methods 10 million times.
print(timeit("{}", number=10**7))
print(timeit("dict()", number=10**7))
Output:
0.5059882
1.6348104
This shows how many seconds it took to create dictionaries in both ways ten million times. As You can see, {} method is approximately three times faster. Of course keep in mind that this will vary from machine to machine and You will see different results here (even if You run this code for let’s say 10 times, You will probably never get the same results, because that depends on Your CPU load. But in general, You should see tangible difference).
So now we know, that creating dictionaries with {} syntax is faster than using dict(). So should we stop using dict()? If Your concert is speed and speed only then I would say yes, but using dict() You can achieve some things easier and make code look cleaner. For example, You can have a list which consists of lists with key, value pairs as in the example below:
sample_list = [["One", 1], ["Four", 4], ["Six", 6]]
Then, You can simply pass this list to dict() construct and it will build a dictionary for You:
my_dict_v1 = dict(sample_list)
print(my_dict_v1)
Output:
{'One': 1, 'Four': 4, 'Six': 6}
Now, if You want to do the same thing using {} construction, You would have to loop through the list object and add new keys and their values to empty dictionary:
sample_list = [["One", 1], ["Four", 4], ["Six", 6]]my_dict_v2 = {}
for key, value in sample_list:
my_dict_v2[key] = value
print(my_dict_v2)
Output:
{'One': 1, 'Four': 4, 'Six': 6}
Of course, if You know about dictionary comprehension, You can do:
my_dict_v3 = {key: value for (key, value) in sample_list}
print(my_dict_v3)
Output:
{'One': 1, 'Four': 4, 'Six': 6}
You can see that the output is the same in all three cases, but personally I find dict() methods easiest to read and understand, because after seeing a word dict I immediately know that we are going to create a dictionary object here. Other constructions requires to read through the code and look what is happening. Of course, this is just my personal opinion, but my goal is not to say which method is the best, but to show what options You have when working with dictionaries so that You could decide which option is better for You. By the way, if You did not understand dictionary comprehension example — don’t worry. It is something that makes sense with a little bit of practice.
Anyways, there is one more thing that I want to share regarding dictionaries creation using dict() constructor — You can actually pass keyword arguments to the constructor and it will create a dictionary:
kwargs_dict = dict(position=4, name="Peter", Color="Blue", id=25663)
print(kwargs_dict)
print(type(kwargs_dict))
Output:
{'position': 4, 'name': 'Peter', 'Color': 'Blue', 'id': 25663}
<class 'dict'>
You can’t do this by using {} construction for dictionaries.
Dictionary methods
In this section I want to quickly review a few most common methods that You are going to need working with dictionaries. So first, let’s create an example dictionary to work with. For simplicity, let’s use the same dictionary as in the previous example, just call it example_dict:
example_dict = dict(position=4, name="Peter", Color="Blue", id=25663)
So what methods You can use when You have this dictionary? One of the most common thing is to know all the keys that Your dictionary has. You can do that by using keys() method:
print(example_dict.keys())
Output:
dict_keys(['position', 'name', 'Color', 'id'])
Why would You need something like dictionary keys? Well, maybe You want to iterate through Your dictionary and access it’s values by the key:
for key in example_dict.keys():
print(f"key:{key}, value:{example_dict[key]}")
Output:
key:position, value:4
key:name, value:Peter
key:Color, value:Blue
key:id, value:25663
Another quite common thing to do is to get all values that are in Your dictionary. You can do that by calling values() method. You would use this when You are interested in dictionary values, but not the keys. For example, imagine that You have a dictionary in which keys are ticket id’s in some sort of issues tracking system and values are number of hours that were spent by programmers or anyone else on that issue. So if You want to know how many hours were spent on the issues (ignoring the issue itself), You could use values() method.
example_dict_v1 = {"Ticket-1": 8, "Ticket-2": 20, "Ticket-3": 36}
print(example_dict_v1.values())
for value in example_dict_v1.values():
print(f"Hours spent:{value}")
Output:
dict_values([8, 20, 36])
Hours spent:8
Hours spent:20
Hours spent:36
Another really useful method when working with dictionaries is get(). You have to pass a key name to the get() method and it returns value associated with the key, but the nice thing about this method is that if the key does not exist in the dictionary, then it returns None. So for example, if we have the same dictionary as in the example above example_dict_v1, then trying to do below will cause exception (because “Ticket-6” key is not present in the dictionary):
hours_on_issue = example_dict_v1["Ticket-6"]
Output:
Traceback (most recent call last):
File "C:/python_projects/dictionaries_review/main.py", line 70, in <module>
hours_on_issue = example_dict_v1["Ticket-6"]
KeyError: 'Ticket-6'
This is not a good thing as it causes Your program to crash. So to avoid this You would have to add try/catch block to handle any unexpected exceptions, but we don’t actually need that since we have get() method — if key is not present in the dictionary, it simply returns None.
hours_on_issue = example_dict_v1.get("Ticket-6")
print(hours_on_issue)
Output:
None
Now what to do if You want to update our dictionary with new value? For example, add a new ticket issue with hours spent on it? Well, we have update() method for that:
example_dict_v1.update({"Ticket-13": 48})
example_dict_v1.update(Ticket16=28)
print(example_dict_v1)
Output:
{'Ticket-1': 8, 'Ticket-2': 20, 'Ticket-3': 36, 'Ticket-13': 48, 'Ticket16': 28}
Note that I have showed two ways here: You can either add new elements as if You are adding another dictionary with {} construct, or You can add new key with value via keyword argument syntax. In any way, You can see that our dictionary was updated successfully with new keys.
Last two methods I wat to discuss are copy() and pop(). I think it is a good idea to talk about them in a single example because it is possible to make some quite nasty bugs if You assign Your dictionary to another one without copy() and perform operations on it. So, let’s say You want to assign Your current dictionary to another object, and remove some key from it. So You can remove any key:value pair by using pop() method and providing a key which You want to remove. But here is the issue — Your new object will actually hold a reference to the original dictionary object, so by making operations on Your new object, You will also perform operations on the original one. Consider example below:
example_dict_v1_copy = example_dict_v1
print(hex(id(example_dict_v1)))
print(hex(id(example_dict_v1_copy)))
example_dict_v1_copy.pop("Ticket-13")
print(example_dict_v1)
print(example_dict_v1_copy)
Output:
0x101a4e0
0x101a4e0
{'Ticket-1': 8, 'Ticket-2': 20, 'Ticket-3': 36, 'Ticket16': 28}
{'Ticket-1': 8, 'Ticket-2': 20, 'Ticket-3': 36, 'Ticket16': 28}
As You can see, I wanted to remove a key from my new dictionary called example_dict_v1_copy without affecting original what, but it is not what happened. You can see that Ticket-13 key was removed from both dictionaries. As I said, it happened because Your new dictionary object was just a reference to the original one (pointing to the memory location where original dictionary was located). It is proven by printing addresses where our objects reside in memory using id() function. That is where copy() method comes in. If You use it on original dictionary object while assigning it to the new one, a new object in memory will be created and original dictionary will not be affected while manipulating the new one:
example_dict_v1_true_copy = example_dict_v1.copy()
print(hex(id(example_dict_v1)))
print(hex(id(example_dict_v1_true_copy)))
example_dict_v1_true_copy.pop("Ticket-1")
print(example_dict_v1)
print(example_dict_v1_true_copy)
Output:
0x101a4e0
0x101a4b0
{'Ticket-1': 8, 'Ticket-2': 20, 'Ticket-3': 36, 'Ticket16': 28}
{'Ticket-2': 20, 'Ticket-3': 36, 'Ticket16': 28}
As You can see, this time original dictionary was not affected, and they are now different objects and You can actually see that by printing where objects reside in memory using id() function again.
Last thing I want to say is that not related to dictionaries, but it can help You to inspect any object in Python. So Python has in-built function called help(). You can pass any object You want to this function and Python will show everything it knows about requested object. You can learn more about it here:
So for example, if You want to know more about dictionaries, You can write:
help(dict)
Output:
Help on class dict in module builtins:class dict(object)
| dict() -> new empty dictionary
| dict(mapping) -> new dictionary initialized from a mapping object's
| (key, value) pairs
| dict(iterable) -> new dictionary initialized as if via:
| d = {}
| for k, v in iterable:
| d[k] = v
| dict(**kwargs) -> new dictionary initialized with the name=value pairs
| in the keyword argument list. For example: dict(one=1, two=2)
|
| Methods defined here:....
I am not going to add full output here because it’s quite long, but I hope You get the idea.
Conclusion
Working with dictionaries in Python can be a little bit tricky. I hope that this article will help You to understand not only dictionaries, but explore other python objects and learn about them faster.
All example code is located under my github repository here: https://github.com/vycart/python_dictionaries
If You have any questions I will be happy to answer them.
Thank You for Reading!