dictionaries¶
This chapter goes over dictionaries in Python. Dictionaries, also called Mappings, are key-value pairs that represent data, values can be any Python object.
I think this is the most important data structure to know as it can hold all the other data structures and in your programming journey you will come across JSON which you can read and write as dictionaries
how to make a dictionary with strings as keys¶
red: make it fail¶
I add a file called test_dictionaries.py
to the tests
folder with the following import statements
import unittest
import dictionaries
the terminal shows ModuleNotFoundError which I add to the list of Exceptions encountered
# Exceptions Encountered
# ModuleNotFoundError
green: make it pass¶
adding a file called dictionaries.py
to the project folder makes the test pass
refactor: make it better¶
I add a failing test to show how to make a dictionary
class TestDictionaries(unittest.TestCase): def test_making_dictionaries_w_strings_as_keys(self): self.assertEqual( dictionaries.a_dict(), {"key": "value"} )
the terminal shows AttributeError which I add to the list of Exceptions encountered
# Exceptions Encountered # ModuleNotFoundError # AttributeError
I add a function definition to
dictionaries.py
def a_dict(): return None
and the terminal shows AssertionError since the function I defined returns None instead of a dictionary
AssertionError: None != {'key': 'value'}
I make the return statement send an empty dictionary
def a_dict(): return {}
the terminal still shows AssertionError but with a return value that looks more like what is expected
E AssertionError: {} != {'key': 'value'}
AssertionError: {} != {'key': 'value'}
shows that 2 values are not equalthe value on the left
{}
is what the function returns, in other words the result of callingdictionaries.a_dict()
from the testthe value on the right
{'key': 'value'}
is what is expected!=
meansnot equal to
I make the return statement with the expected values and I get a passing test. YES! We are off to a good start
def a_dict(): return {'key': 'value'}
it is also possible to make a dictionary using the dict constructor. I add another test to
test_making_dictionaries_w_strings_as_keys
def test_making_dictionaries_w_strings_as_keys(self): self.assertEqual( dictionaries.a_dict(), {'key': 'value'} ) self.assertEqual( dictionaries.a_dict(), dict(key='key') )
the terminal shows AssertionError
AssertionError: {'key': 'value'} != {'key': 'key'}
so I change the test to make it pass
self.assertEqual( dictionaries.a_dict(), dict(key='value') )
the terminal shows passing tests, because
dict(key='value')
and{'key': 'value'}
are 2 ways of representing the same thingI can add another test to confirm, even though it repeats the 2 tests above
def test_making_dictionaries_w_strings_as_keys(self): self.assertEqual( dictionaries.a_dict(), {"key": "value"} ) self.assertEqual( dictionaries.a_dict(), dict(key='value') ) self.assertEqual( {"key": "value"}, dict(key='key') )
the terminal shows AssertionError
AssertionError: {'key': 'value'} != {'key': 'key'}
I change the test to make it pass
self.assertEqual( {"key": "value"}, dict(key='value') )
how to make a dictionary with numbers as keys¶
red: make it fail¶
I add a failing test to TestDictionaries
def test_making_dictionaries_w_numbers_as_keys(self):
self.assertEqual(
{1: 'boom'},
{'one': 'boom'}
)
the terminal shows AssertionError since the 2 values are different
AssertionError: {1: 'boom'} != {'one': 'boom'}
green: make it pass¶
I make the values in the test to make it pass
def test_making_dictionaries_w_numbers_as_keys(self):
self.assertEqual(
{1: 'boom'},
{1: 'boom'}
)
the terminal shows passing tests confirming that integers can be used as dictionary keys
refactor: make it better¶
I know I can use integers and strings as dictionary keys. I want to add a test to see if I can use floats
def test_making_dictionaries_w_numbers_as_keys(self): self.assertEqual( {1: 'boom'}, {1: 'boom'} ) self.assertEqual( {2.5: 'works'}, {2.5: 'fails'} )
the terminal shows AssertionError since the values are different
AssertionError: {2.5: 'works'} != {2.5: 'fails'} - {2.5: 'works'} + {2.5: 'fails'}
I make the values in the test to make it pass
def test_making_dictionaries_w_numbers_as_keys(self): self.assertEqual( {1: 'boom'}, {1: 'boom'} ) self.assertEqual( {2.5: 'works'}, {2.5: 'works'} )
the terminal shows passing tests confirming that I can use integers and floats as dictionary keys
how to make a dictionary with booleans as keys¶
I wonder if it is possible to use False or True as dictionary keys
red: make it fail¶
I add a test to find out if it is possible to use False as a dictionary key
def test_making_dictionaries_w_booleans_as_keys(self):
self.assertEqual(
{False: 'boom'},
{False: 'bap'}
)
the terminal shows AssertionError
AssertionError: {False: 'boom'} != {False: 'bap'}
- {False: 'boom'}
? ^^^
+ {False: 'bap'}
? ^^
green: make it pass¶
I make the values to make them match and tests are green again. Sweet!
def test_making_dictionaries_w_booleans_as_keys(self):
self.assertEqual(
{False: 'boom'},
{False: 'boom'}
)
I can use False as a key in a dictionary
refactor: make it better¶
I add a test to find out if it is possible to use True as a dictionary key
def test_making_dictionaries_w_booleans_as_keys(self): self.assertEqual( {False: 'boom'}, {False: 'boom'} ) self.assertEqual( {True: 'bap'}, {True: 'boom'} )
the terminal shows AssertionError
AssertionError: {True: 'bap'} != {True: 'boom'} - {True: 'bap'} ? ^^ + {True: 'boom'} ?
and I make the values to make the tests pass
def test_making_dictionaries_w_booleans_as_keys(self): self.assertEqual( {False: 'boom'}, {False: 'boom'} ) self.assertEqual( {True: 'bap'}, {True: 'bap'} )
So far from the tests, I see that I can use booleans, floats, integers and strings as dictionary keys
how to make a dictionary with tuples as keys¶
red: make it fail¶
I add a test to TestDictionaries
to see if I can use tuples as dictionary keys
def test_making_dictionaries_w_tuples_as_keys(self):
self.assertEqual(
{(1, 2): "value"},
{(1, 2): "key"}
)
the terminal shows AssertionError
AssertionError: {(1, 2): 'value'} != {(1, 2): 'key'}
- {(1, 2): 'value'}
? ^^^^
+ {(1, 2): 'key'}
? ^ +
green: make it pass¶
I make the values to make the test pass
self.assertEqual(
{(1, 2): "value"},
{(1, 2): "value"}
)
the tests so far show that I can use tuples, booleans, floats, integers, and strings as dictionary keys
Can I make a Dictionary with a list as a key?¶
red: make it fail¶
I add a test to TestDictionaries
using a list as a key
def test_making_dictionaries_w_lists_as_keys(self):
{[1, 2]: "BOOM"}
the terminal shows TypeError because only hashable types can be used as dictionary keys and lists are not hashable
E TypeError: unhashable type: 'list'
I add TypeError to the list of Exceptions encountered
# Exceptions Encountered
# ModuleNotFoundError
# AttributeError
# TypeError
green: make it pass¶
I can use self.assertRaises
to confirm that an error is raised by some code without having it crash the tests. I use it here to confirm that Python raises TypeError when I try to make a dictionary with a list as the key
def test_making_dictionaries_w_lists_as_keys(self):
with self.assertRaises(TypeError):
{[1, 2]: "BOOM"}
see how to test that an Exception is raised for more details on why that worked.
From the test I see that I cannot make a dictionary with a list as a key
Can I make a Dictionary with a set as a key?¶
I try a similar test using a set as a key
red: make it fail¶
def test_making_dictionaries_w_sets_as_keys(self):
{{1, 2}: "BOOM"}
the terminal shows TypeError
TypeError: unhashable type: 'set'
green: make it pass¶
I use self.assertRaises
to handle the exception
def test_making_dictionaries_w_sets_as_keys(self):
with self.assertRaises(TypeError):
{{1, 2}: "BOOM"}
Tests are green again. I cannot use a set or a list as a dictionary key
Can I make a Dictionary with a dictionary as a key?¶
red: make it fail¶
I add a new test
def test_making_dictionaries_w_dictionaries_as_keys(self):
a_dictionary = {"key": "value"}
{a_dictionary: "BOOM"}
and the terminal shows TypeError
> {a_dictionary: "BOOM"}
E TypeError: unhashable type: 'dict'
green: make it pass¶
I add an how to test that an Exception is raised to the test to confirm the findings
def test_making_dictionaries_w_dictionaries_as_keys(self):
a_dictionary = {"key": "value"}
with self.assertRaises(TypeError):
{a_dictionary: "BOOM"}
and the terminal shows passing tests. I cannot use a dictionary, set or a list as a dictionary key
from these tests I know that I can make dictionaries with the following data structures as keys
and I cannot make dictionaries with the following data structures as keys
how to access dictionary values¶
The tests so far show how to make dictionaries and what objects can be used as keys
. The following tests show how to access the values of a dictionary
red: make it fail¶
I add a test to TestDictionaries
in test_dictionaries.py
def test_accessing_dictionary_values(self):
a_dictionary = {"key": "value"}
self.assertEqual(a_dictionary["key"], "bob")
the terminal shows AssertionError because bob
is not equal to value
. I can get a value for a key by providing the key in square brackets to the dictionary
AssertionError: 'value' != 'bob'
green: make it pass¶
I make the expected value to make the tests pass
def test_accessing_dictionary_values(self):
a_dictionary = {"key": "value"}
self.assertEqual(a_dictionary["key"], "value")
refactor: make it better¶
I can also show all the values of a dictionary as a list without the keys
def test_listing_dictionary_values(self): a_dictionary = { 'key1': 'value1', 'key2': 'value2', 'key3': 'value3', 'keyN': 'valueN', } self.assertEqual( list(a_dictionary.values()), [] )
the terminal shows AssertionError
AssertionError: Lists differ: ['value1', 'value2', 'value3', 'valueN'] != []
the tests pass when I make the values in the test to make them match the result
def test_listing_dictionary_values(self): a_dictionary = { 'key1': 'value1', 'key2': 'value2', 'key3': 'value3', 'keyN': 'valueN', } self.assertEqual( list(a_dictionary.values()), [ 'value1', 'value2', 'value3', 'valueN', ] )
I can also show the keys of a dictionary as a list
def test_listing_dictionary_keys(self): a_dictionary = { 'key1': 'value1', 'key2': 'value2', 'key3': 'value3', 'keyN': 'valueN', } self.assertEqual( list(a_dictionary.keys()), [] )
the terminal shows AssertionError
AssertionError: Lists differ: ['key1', 'key2', 'key3', 'keyN'] != []
I add the values to the empty list in the test to make it pass
def test_listing_dictionary_keys(self): a_dictionary = { 'key1': 'value1', 'key2': 'value2', 'key3': 'value3', 'keyN': 'valueN', } self.assertEqual( list(a_dictionary.keys()), [ 'key1', 'key2', 'key3', 'keyN', ] )
how to get a value when the key does not exist¶
Sometimes when I try to access values in a dictionary, I use a key that does not exist or misspell a key that does exist
red: make it fail¶
I add a test for both cases
def test_dictionaries_raise_key_error_when_key_does_not_exist(self):
a_dictionary = {
'key1': 'value1',
'key2': 'value2',
'key3': 'value3',
'keyN': 'valueN',
}
a_dictionary['non_existent_key']
a_dictionary['ky1']
and the terminal shows a KeyError
> a_dictionary['non_existent_key']
E KeyError: 'non_existent_key'
KeyError is raised when a dictionary is called with a key
that does not exist.
green: make it pass¶
I add KeyError to the list of Exceptions encountered
# Exceptions Encountered # ModuleNotFoundError # AttributeError # TypeError # KeyError
then add an how to test that an Exception is raised to confirm that the error is raised
def test_dictionaries_raise_key_error_when_key_does_not_exist(self): a_dictionary = { 'key1': 'value1', 'key2': 'value2', 'key3': 'value3', 'keyN': 'valueN', } with self.assertRaises(KeyError): a_dictionary['non_existent_key'] a_dictionary['ky1']
the terminal shows KeyError for the next line where I misspelled the key
> a_dictionary['ky1'] E KeyError: 'ky1'
and I add it to the how to test that an Exception is raised to make the test pass
def test_dictionaries_raise_key_error_when_key_does_not_exist(self): a_dictionary = { 'key1': 'value1', 'key2': 'value2', 'key3': 'value3', 'keyN': 'valueN', } with self.assertRaises(KeyError): a_dictionary['non_existent_key'] a_dictionary['ky1']
refactor: make it better¶
What if I want to access a dictionary with a key that does not exist and not have Python raise an error when it does not find the key?
I add a test called
test_how_to_get_a_value_when_a_key_does_not_exist
toTestDictionaries
def test_how_to_get_a_value_when_a_key_does_not_exist(self): a_dictionary = { 'key1': 'value1', 'key2': 'value2', 'key3': 'value3', 'keyN': 'valueN', } self.assertIsNone(a_dictionary['non_existent_key'])
the terminal shows KeyError because
non_existent_key
does not exist ina_dictionary
> self.assertIsNone(a_dictionary['non_existent_key']) E KeyError: 'non_existent_key'
I can use the get method when I do not wantPython to raise KeyError for a key that does not exist
def test_how_to_get_a_value_when_a_key_does_not_exist(self): a_dictionary = { 'key1': 'value1', 'key2': 'value2', 'key3': 'value3', 'keyN': 'valueN', } self.assertIsNone(a_dictionary.get('non_existent_key'))
the terminal shows a passing test. This means that when I use the get method and the
key
does not exist, I get None as the result.I can state the above explicitly, from the Zen of Python:
Explicit is better than implicit
def test_how_to_get_a_value_when_a_key_does_not_exist(self): a_dictionary = { 'key1': 'value1', 'key2': 'value2', 'key3': 'value3', 'keyN': 'valueN', } self.assertIsNone(a_dictionary.get('non_existent_key')) self.assertIsNone(a_dictionary.get('non_existent_key', False))
the terminal shows AssertionError because False is not None
> self.assertIsNone(a_dictionary.get('non_existent_key', False)) E AssertionError: False is not None
so I make the value to make the test pass
self.assertIsNone(a_dictionary.get('non_existent_key', None))
the terminal shows passing tests.
The get method takes in 2 inputs
the
key
the
default value
wanted when thekey
does not exist
I can also use the get method to get the value for an existing key
def test_how_to_get_a_value_when_a_key_does_not_exist(self): a_dictionary = { 'key1': 'value1', 'key2': 'value2', 'key3': 'value3', 'keyN': 'valueN', } self.assertIsNone(a_dictionary.get('non_existent_key')) self.assertIsNone(a_dictionary.get('non_existent_key', None)) self.assertEqual(a_dictionary.get('key1', None), None)
the terminal shows AssertionError because
value1
which is the value forkey1
ina_dictionary
is not equal to None> self.assertEqual(a_dictionary.get('key1', None), None) E AssertionError: 'value1' != None
I change the test to make it pass.
def test_how_to_get_a_value_when_a_key_does_not_exist(self): a_dictionary = { 'key1': 'value1', 'key2': 'value2', 'key3': 'value3', 'keyN': 'valueN', } self.assertIsNone(a_dictionary.get('non_existent_key')) self.assertIsNone(a_dictionary.get('non_existent_key', None)) self.assertEqual(a_dictionary.get('key1', None), 'value1')
Do you think you could write an implementation for the get
method after reading how to handle Exceptions in programs?
how to view the attributes and methods of a dictionary¶
The chapter on classes shows how to view the attributes and methods of an object. Let us look at the attributes and methods of dictionaries
red: make it fail¶
I add a new test to TestDictionaries
def test_dictionary_attributes(self):
self.maxDiff = None
self.assertEqual(
dir(dictionaries.a_dict()),
[]
)
the terminal shows AssertionError
AssertionError: Lists differ: ['__class__', '__class_getitem__', '__cont[530 chars]ues'] != []
green: make it pass¶
I copy the expected values shown in the terminal to make the test pass
Note
Your results may vary based on your version of Python
def test_dictionary_attributes(self):
self.maxDiff = None
self.assertEqual(
dir(dictionaries.a_dict()),
[
'__class__',
'__class_getitem__',
'__contains__',
'__delattr__',
'__delitem__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__getitem__',
'__getstate__',
'__gt__',
'__hash__',
'__init__',
'__init_subclass__',
'__ior__',
'__iter__',
'__le__',
'__len__',
'__lt__',
'__ne__',
'__new__',
'__or__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__reversed__',
'__ror__',
'__setattr__',
'__setitem__',
'__sizeof__',
'__str__',
'__subclasshook__',
'clear',
'copy',
'fromkeys',
'get',
'items',
'keys',
'pop',
'popitem',
'setdefault',
'update',
'values'
]
)
refactor: make it better¶
I see some of the methods I have tested so far and others I did not. You can write tests for the others to show what they do and/or read more about dictionaries.
get - gets the
value
for akey
and returns a default value or None when the key does not existkeys - returns a view of the
keys
in a dictionaryvalues - returns a view of the
values
in a dictionary
how to set a default value for a given key¶
Let us say I want to find out more about the setdefault method
red: make it fail¶
I add a failing test
def test_set_default_for_a_given_key(self):
a_dictionary = {'bippity': 'boppity'}
a_dictionary['another_key']
and the terminal shows KeyError
green: make it pass¶
I add self.assertRaises
to confirm that KeyError gets raised for the test to pass
def test_set_default_for_a_given_key(self):
a_dictionary = {'bippity': 'boppity'}
with self.assertRaises(KeyError):
a_dictionary['another_key']
refactor: make it better¶
Then I add a test for setdefault
def test_set_default_for_a_given_key(self): a_dictionary = {'bippity': 'boppity'} with self.assertRaises(KeyError): a_dictionary['another_key'] a_dictionary.setdefault('another_key') self.assertEqual( a_dictionary, {'bippity': 'boppity'} )
the terminal shows AssertionError because
a_dictionary
has changed, it has a new key which was not there beforeAssertionError: {'bippity': 'boppity', 'another_key': None} != {'bippity': 'boppity'} - {'another_key': None, 'bippity': 'boppity'} + {'bippity': 'boppity'}
I change the test to make it pass
def test_set_default_for_a_given_key(self): a_dictionary = {'bippity': 'boppity'} with self.assertRaises(KeyError): a_dictionary['another_key'] a_dictionary.setdefault('another_key') self.assertEqual( a_dictionary, { 'bippity': 'boppity', 'another_key': None } )
when I first try to access the value for
another_key
ina_dictionary
I get KeyError because it does not exist in the dictionaryafter using setdefault and passing in
another_key
as the key, it gets added to the dictionary so I do not get KeyError when I try to access it again
def test_set_default_for_a_given_key(self): a_dictionary = {'bippity': 'boppity'} with self.assertRaises(KeyError): a_dictionary['another_key'] a_dictionary.setdefault('another_key') self.assertEqual( a_dictionary, { 'bippity': 'boppity', 'another_key': None } ) self.assertIsNone(a_dictionary['another_key'])
I now add a test for setting the default value to something other th None
a_dictionary.setdefault('a_new_key', 'a_default_value') self.assertEqual( a_dictionary, { 'bippity': 'boppity', 'another_key': None } )
the terminal shows AssertionError since
a_dictionary
now has a newkey
andvalue
AssertionError: {'bippity': 'boppity', 'another_key': None, 'a_new_key': 'a_default_value'} != {'bippity': 'boppity', 'another_key': None} - {'a_new_key': 'a_default_value', 'another_key': None, 'bippity': 'boppity'} + {'another_key': None, 'bippity': 'boppity'}
I add the new values to the test to make it pass
self.assertEqual( a_dictionary, { 'bippity': 'boppity', 'another_key': None, 'a_new_key': 'a_default_value', } )
all tests pass, and I add what I know about setdefault to the list of attributes and methods of dictionaries
test_adding_2_dictionaries¶
What if I want to add the keys
and values
of one dictionary to another?
red: make it fail¶
I add another test to TestDictionaries
def test_adding_two_dictionaries(self):
a_dictionary = {
"basic": "toothpaste",
"whitening": "peroxide",
}
a_dictionary.update({
"traditional": "chewing stick",
"browning": "tobacco",
"decaying": "sugar",
})
self.assertEqual(
a_dictionary,
{
"basic": "toothpaste",
"whitening": "peroxide",
}
)
the terminal shows AssertionError because the values of a_dictionary
were changed when I called the update method on it
AssertionError: {'bas[37 chars]xide', 'traditional': 'chewing stick', 'browni[31 chars]gar'} != {'bas[37 chars]xide'}
+ {'basic': 'toothpaste', 'whitening': 'peroxide'}
- {'basic': 'toothpaste',
- 'browning': 'tobacco',
- 'decaying': 'sugar',
- 'traditional': 'chewing stick',
- 'whitening': 'peroxide'}
green: make it pass¶
I make the values to make the test pass
def test_adding_two_dictionaries(self):
a_dictionary = {
"basic": "toothpaste",
"whitening": "peroxide",
}
a_dictionary.update({
"traditional": "chewing stick",
"browning": "tobacco",
"decaying": "sugar",
})
self.assertEqual(
a_dictionary,
{
"basic": "toothpaste",
"whitening": "peroxide",
"traditional": "chewing stick",
"browning": "tobacco",
"decaying": "sugar",
}
)
how to remove an item from a dictionary¶
I can remove an item from a dictionary with the pop method. It deletes the key
and value
from the dictionary and returns the value
red: make it fail¶
I add a failing test to TestDictionaries
def test_pop(self):
a_dictionary = {
"basic": "toothpaste",
"whitening": "peroxide",
"traditional": "chewing stick",
"browning": "tobacco",
"decaying": "sugar",
}
self.assertEqual(a_dictionary.pop("basic"), None)
the terminal shows AssertionError
> self.assertEqual(a_dictionary.pop("basic"), None)
E AssertionError: 'toothpaste' != None
green: make it pass¶
I add the right value to the test to make it pass
def test_pop(self): a_dictionary = { "basic": "toothpaste", "whitening": "peroxide", "traditional": "chewing stick", "browning": "tobacco", "decaying": "sugar", } self.assertEqual(a_dictionary.pop("basic"), "toothpaste")
then add a test to confirm that
a_dictionary
has changeddef test_pop(self): a_dictionary = { "basic": "toothpaste", "whitening": "peroxide", "traditional": "chewing stick", "browning": "tobacco", "decaying": "sugar", } self.assertEqual(a_dictionary.pop("basic"), "toothpaste") self.assertEqual( a_dictionary, { "basic": "toothpaste", "whitening": "peroxide", "traditional": "chewing stick", "browning": "tobacco", "decaying": "sugar", } )
the terminal shows AssertionError confirming that
a_dictionary
is differentAssertionError: {'whitening': 'peroxide', 'traditional': 'c[53 chars]gar'} != {'basic': 'toothpaste', 'whitening': 'perox[76 chars]gar'} + {'basic': 'toothpaste', - {'browning': 'tobacco', ? ^ + 'browning': 'tobacco', ? ^ 'decaying': 'sugar', 'traditional': 'chewing stick', 'whitening': 'peroxide'}
The test passes when I remove the key-value pairs of
basic
andtoothpaste
def test_pop(self): a_dictionary = { "basic": "toothpaste", "whitening": "peroxide", "traditional": "chewing stick", "browning": "tobacco", "decaying": "sugar", } self.assertEqual(a_dictionary.pop("basic"), "toothpaste") self.assertEqual( a_dictionary, { "whitening": "peroxide", "traditional": "chewing stick", "browning": "tobacco", "decaying": "sugar", } )
I ran tests to show
how to make a dictionary
What objects can be used as dictionary keys
What objects cannot be used as dictionary keys
how to view dictionary keys
how to view dictionary values
how to view the attributes and methods of a dictionary
how to set a default value for a key
how to change a dictionary with another dictionary and
how to remove an item from a dictionary
I alsop ran into the following Exceptions
Would you like to test functions?