Learn Python Series (#7) - Handling Dictionaries

in utopian-io •  7 years ago  (edited)

Learn Python Series (#7) - Handling Dictionaries

python_logo.png

What Will I Learn?

  • You will learn how to create and copy dictionaries using various techniques,
  • how to get (access) and set (add or modify) dictionary items / item values,
  • how to remove them,
  • you will learn about dictionary view objects and methods, and
  • about iterating over dictionaries.

Requirements

  • A working modern computer running macOS, Windows or Ubuntu
  • An installed Python 3(.6) distribution, such as (for example) the Anaconda Distribution
  • The ambition to learn Python programming

Difficulty

Intermediate

Curriculum (of the Learn Python Series):

Learn Python Series (#7) - Handling Dictionaries

In this Learn Python Series episode we will be reviewing the built-in Python data type dictionary.

In this episode you will learn how to create and copy dictionaries using various techniques, how to get (access) and set (add or modify) dictionary items / item values, how to remove them, you will learn about dictionary view objects and methods, and about iterating over dictionaries.

What is a dictionary actually?

A dictionary is a mapping object (currently the only standard one), meaning that it "maps" hashable mutable values to immutable keys, making the mappings themselves mutable.

A dictionary stores data as an unordered collection of {key:value} pairs. (Most) other data types just have an element value. A dictionary value can be of any data type (meaning we can easily create nested dictionaries because the value can be another dictionary), but a key must be an immutable data type (like a tuple, a string, or a number).

Creating and copying dictionaries

Creating using {} versus dict()

You can create a new dictionary either by using curly braces {}, or by using the built-in dict() function. It can be empty while initiating it, or filled with some key:value pairs right away. Like so:

# Create an empty dictionary using curly braces `{}`
dict_1 = {}
print(dict_1)
# {}

# Create an empty dictionary using `dict()`
dict_2 = dict()
print(dict_2)
# {}

# Create a dictionary with mixed keys 
# (strings and integers)
dict_3 = {'name': 'scipio', 
          'roles': ['advisor', 'moderator', 'contributor'],
          1: 'banana',
          2: 'dog' }
print(dict_3)
'''
{'name': 'scipio', 
 'roles': ['advisor', 'moderator', 'contributor'], 
 1: 'banana', 
 2: 'dog'}
'''
{}
{}
{'name': 'scipio', 'roles': ['advisor', 'moderator', 'contributor'], 1: 'banana', 2: 'dog'}

Creating using dict.fromkeys(sequence[, value])

The dict.fromkeys() method can be used to create a dictionary by using the values contained in a sequence (such as a string or a list). Optionally a value argument may be set, via which every dictionary will be set to that same corresponding value.

Please observe the following examples:

# Create dictionary from list
my_list = ['a', 'b', 'c']
my_dict = dict.fromkeys(my_list)
print(my_dict)
# {'a': None, 'b': None, 'c': None}

# Create dictionary from string
my_string = 'scipio'
my_dict = {}.fromkeys(my_string)
print(my_dict)
# {'s': None, 'c': None, 'i': None, 'p': None, 'o': None}
# .....
# Notice there are two 'i' characters
# in the string "scipio",
# and only one `i` key in the dictionary

# Pass a default `value` argument
my_keys = 'abcde'
my_val = 100
my_dict = {}.fromkeys(my_keys, my_val)
print(my_dict)
# {'a': 100, 'b': 100, 'c': 100, 'd': 100, 'e': 100}
{'a': None, 'b': None, 'c': None}
{'s': None, 'c': None, 'i': None, 'p': None, 'o': None}
{'a': 100, 'b': 100, 'c': 100, 'd': 100, 'e': 100}

Copying using = versus dict.copy()

As is the case with lists, it is possible to copy dictionaries using the = operator. But if you change one copy (for example using dict.update() explained below), you also change the other. This is because the = operator only creates a reference to the original dictionary. For example:

a = {'one': 1, 'two': 2, 'three': 3}
b = a
b.update({'two': 9})
print(a, b)
# {'one': 1, 'two': 9, 'three': 3} {'one': 1, 'two': 9, 'three': 3}
{'one': 1, 'two': 9, 'three': 3} {'one': 1, 'two': 9, 'three': 3}

To avoid this behavior, use dict.copy() to create a so-called shallow copy, where a new dictionary is created. Like so:

a = {'one': 1, 'two': 2, 'three': 3}
b = a.copy()
b.update({'two': 9})
print(a,b)
# `a` now stays unmodified
# {'one': 1, 'two': 2, 'three': 3} {'one': 1, 'two': 9, 'three': 3}
{'one': 1, 'two': 2, 'three': 3} {'one': 1, 'two': 9, 'three': 3}

Getting & setting dictionary items

Getting values using [] versus dict.get()

Up until now, using strings, tuples and lists, we were accessing element values by using their index / position in the data container. But because dictionaries have no order, you can't access the dictionary items via their index. The most common way to access individual dictionary items is by using their key, either with a squared bracket [] notation or by using the get() method.

Please note, that if the key was not found inside the dictionary, [] raises a KeyError, while get() will return None.

For example:

dict_3 = {'name': 'scipio',
          'roles': ['advisor', 'moderator', 'contributor'],
          1: 'banana',
          2: 'dog' }

# Access elements using `[]`
name = dict_3['name']
print(name)
# scipio

# Access elements using `get()`
roles = dict_3.get('roles')
print(roles)
# ['advisor', 'moderator', 'contributor']
scipio
['advisor', 'moderator', 'contributor']

Getting & setting using dict.setdefault(key[, default_value])

The dict.setdefault() method gets item values by key, similar to dict.get(), but - in case the key is not present in the dictionary - it sets dict[key]. In case of a set, when no default value is passed as an argument, then None will be set, otherwise the default value will be set.

Like so:

account = {'name': 'scipio', 'age_days': 133}

# Get value with key present, no default
name = account.setdefault('name')
print(name, account)
# scipio {'name': 'scipio', 'age_days': 133}

# Get value with key present, default
age = account.setdefault('age_days', 365)
print(age, account)
# 133 {'name': 'scipio', 'age_days': 133}
# PS: because the key is present, nothing is set / modified

# Get value with key absent, no default: 
# `None` value is set.
sbd = account.setdefault('sbd')
print(sbd, account)
# None {'name': 'scipio', 'age_days': 133, 'sbd': None}

# Get value with key absent, no default: 
# `Default argument` value is set.
steempower = account.setdefault('steempower', 999999)
print(steempower, account)
# 999999 {'name': 'scipio', 'age_days': 133, 'sbd': None, 'steempower': 999999}
scipio {'name': 'scipio', 'age_days': 133}
133 {'name': 'scipio', 'age_days': 133}
None {'name': 'scipio', 'age_days': 133, 'sbd': None}
999999 {'name': 'scipio', 'age_days': 133, 'sbd': None, 'steempower': 999999}

Updating (modifying) and adding dictionairy items

Using the = operator

Because dictionaries are mutable, we can simply add and modify its using the assignment = operator. The syntax is: dict[key] = value. If the key isn't there, it gets added, and if it is, it gets modified. For example:

my_dict = {'a': 100, 'b': 50}
print(my_dict)
# {'a': 100, 'b': 50}

# adding an item
my_dict['c'] = 100
print(my_dict)
# {'a': 100, 'b': 50, 'c': 100}

# modifying an item
my_dict['a'] = 200
print(my_dict)
# {'a': 100, 'b': 50, 'c': 100}
{'a': 100, 'b': 50}
{'a': 100, 'b': 50, 'c': 100}
{'a': 200, 'b': 50, 'c': 100}

Using the dict.update() method

The update() method updates existing items or adds then when absent. As the update() argument a dictionary, a list of 2-tuples, or keyword arguments are accepted. This works as follows:

test = {'a': 1, 'b': 2}

# update with dictionary
test.update({'a': 100, 'b': 200, 'c': 300})
print(test)
# {'a': 100, 'b': 200, 'c': 300}

# update with a list of 2-tuples
test.update([('d', 40),('a', 10),('c', 10)])
print(test)
# {'a': 10, 'b': 200, 'c': 10, 'd': 40}

# update with keyword arguments
test.update(a=50, b=100, c=150, d=200)
print(test)
{'a': 100, 'b': 200, 'c': 300}
{'a': 10, 'b': 200, 'c': 10, 'd': 40}
{'a': 50, 'b': 100, 'c': 150, 'd': 200}

Removing dictionary items

Python has various "dictionary-only" methods and built-in functions for removing items from a dictionary, or the entire dictionary itself. We will hereby discuss (most of) them.

Using the dict.pop(key[, default]) method

The pop() method looks if a given key is in the dictionary, and if so, it both removes it and returns its value. When the key is not in the dictionary, either a KeyError is raised or, if set, the default value is returned. Please observe the following examples:

my_dict1 = {'one': 1, 'two': 2, 'three': 3}

# Remove and return `'three'`
res = my_dict1.pop('three')
print(res, my_dict1)
# 3 {'one': 1, 'two': 2}
3 {'one': 1, 'two': 2}
# Remove and return `'four'`, which is absent,
# so in turn return the default `9` value,
# and not a KeyError
my_dict2 = {'one': 1, 'two': 2, 'three': 3, 'five': 5}
res = my_dict2.pop('four', 9)
print(res, my_dict2)
# 9 {'one': 1, 'two': 2, 'three': 3, 'five': 5}
9 {'one': 1, 'two': 2, 'three': 3, 'five': 5}

Using the dict.popitem() method

The popitem() method doesn't accept any argument, and removes and returns an arbitrary item from the dictionary. Like is the case with dict.pop(), in case popitem() is called at an empty dictionary, a KeyError is raised.

Question: Why is popitem() useful, if you're unable to determine which key will be removed and returned?
Answer: popitem() can be be used to destructively iterate, using a loop, over a dictionary.

Please regard the following examples:

test1 = {'one': 1, 'two': 2, 'three': 3}

# Remove and return one existing item
result = test1.popitem()
print(result)
print(test1)
# ('three', 3)
# {'one': 1, 'two': 2}
('three', 3)
{'one': 1, 'two': 2}
test2 = {'one': 1, 'two': 2, 'three': 3}

# Create a `destruct_dict()` function
def destruct_dict(dict):
    while len(dict) > 0:
        print(dict.popitem(), dict)
    else:
        print('Done!')

# Call `destruct_dict()`
destruct_dict(test2)
# ('three', 3) {'one': 1, 'two': 2}
# ('two', 2) {'one': 1}
# ('one', 1) {}
# Done!
('three', 3) {'one': 1, 'two': 2}
('two', 2) {'one': 1}
('one', 1) {}
Done!

PS: did you notice that I now used a while .. else clause? ;-)

Using the dict.clear() method

Like lists, dictionaries also have a clear() method, which removes all items contained in a dictionary.

test3 = {'one': 1, 'two': 2, 'three': 3}
test3.clear()
print(test3)
# {}
{}

Using the del statement

As is the case with lists, dictionaries can also make use of the del statement to remove items from a dictionary or delete the entire dictionary. For example:

test4 = {'one': 1, 'two': 2, 'three': 3}

# remove one item
del test4['three']
print(test4)
# {'one': 1, 'two': 2}

# remove the entire dictionary
del test4
{'one': 1, 'two': 2}

Dictionary view objects

A dictionary can use the methods dict.keys(), dict.values(), and dict.items(). The objects that are returned are so-called view objects and they are iterable.

When iterating (using a loop) over dictionary keys and values, the order in which that happens is arbitrary, but non-random, dependent on when what was added, deleted and modified on the dictionary, and the order varies per Python distribution.

However, when iterating over dictionary views, the keys and values are iterated over in the same order, which allows for zipping (value, key) and (key, value) pairs using zip().

For example:

# Iterate over the dict numerical values 
# and sum them.
a = {'one': 1, 'two': 2, 'three': 3}
values = a.values()
total = 0
for val in values:
    total += val    
print(total)
# 6

# Iterating over keys and values is done
# in the same order for keys and values
b = {'dogs': 10, 'cats': 15, 'cows': 20}
keys = b.keys()
values = b.values()
print(list(keys))
print(list(values))
# ['dogs', 'cats', 'cows']
# [10, 15, 20]

# .....

# And because the keys and values are 
# in the same order, that allows for
# zipping the key/value views as pairs
pairs = list(zip(keys, values))
print(pairs)
# [('dogs', 10), ('cats', 15), ('cows', 20)]

# ...

# Same thing using `dict.items()`
c = {'account': 'utopian-io', 'rep': 66.1, 'age_days': 159}
items = c.items()
print(list(items))
6
['dogs', 'cats', 'cows']
[10, 15, 20]
[('dogs', 10), ('cats', 15), ('cows', 20)]
[('account', 'utopian-io'), ('rep', 66.1), ('age_days', 159)]

Handling nested dictionaries

Up until now, we've been discussing "flat" dictionary structures. But in case a dictionary value is a dictionary itself, ergo, when a value itself consists of one or more key:value pairs, then, in that case, a dictionary is contained within another dictionary, which is called a nested dictionary.

For example:

# `dict_nested` holds 2 dicts:
# `dict1` and `dict2`
dict_nested = {
    'dict1': {'key': 1},
    'dict2': {'key': 5}
}
print(dict_nested)
# {'dict1': {'key': 1}, 'dict2': {'key': 5}}
{'dict1': {'key': 1}, 'dict2': {'key': 5}}

Getting and setting nested items

All the previously discussed dictionary methods and techniques apply to nested dictionaries as well, for example using the squared bracket [] notation, repeatedly.

countries = {
    'USA': {'capital': 'Washington, D.C.', 'main_language': 'English'},
    'Germany': {'capital': 'Berlin', 'main_language': 'German'},
    'France': {'capital': 'Paris', 'main_language': 'French'},
    'The Netherlands': {'capital': 'Amsterdam', 'main_language': 'Dutch'}
}

# get individual values
print(countries['USA']['main_language'])
print(countries['The Netherlands']['capital'])
English
Amsterdam
countries = {
    'USA': {'capital': 'Washington, D.C.', 'main_language': 'English'},
    'Germany': {'capital': 'Berlin', 'main_language': 'German'},
    'France': {'capital': 'Paris', 'main_language': 'French'},
    'The Netherlands': {'capital': 'Amsterdam', 'main_language': 'Dutch'}
}

# iterating over the outer dict keys
for country in countries:
    print(country)
USA
Germany
France
The Netherlands
countries = {
    'USA': {'capital': 'Washington, D.C.', 'main_language': 'English'},
    'Germany': {'capital': 'Berlin', 'main_language': 'German'},
    'France': {'capital': 'Paris', 'main_language': 'French'},
    'The Netherlands': {'capital': 'Amsterdam', 'main_language': 'Dutch'}
}

# iterating over outer items and inner keys
for country, data in countries.items():
    print('Country:',country)
    print('------------------')
    for key in data:
        print(key + ':\t' + data[key])
    print('\n')
Country: USA
------------------
capital:    Washington, D.C.
main_language:  English


Country: Germany
------------------
capital:    Berlin
main_language:  German


Country: France
------------------
capital:    Paris
main_language:  French


Country: The Netherlands
------------------
capital:    Amsterdam
main_language:  Dutch

What did we learn, hopefully?

That dictionaries are a very interesting and useful data type, what dictionaries are and how to use them. In the next few episodes we will continue discussing some more built-in Python data types. See you there!

Thank you for your time!



Posted on Utopian.io - Rewarding Open Source Contributors

Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!
Sort Order:  

You are very intelligent @scipio

Thanks for your helpful blog @scipio

Indeed. You must be very intelligent @scipio!

Thank you for the contribution. It has been approved.

  • Life is short , we use Python

You can contact us on Discord.
[utopian-moderator]

This post has been upvoted and picked by Daily Picked #3! Thank you for the cool and quality content. Keep going!

Don’t forget I’m not a robot. I explore, read, upvote and share manually :)

Hey @scipio I am @utopian-io. I have just upvoted you!

Achievements

  • WOW WOW WOW People loved what you did here. GREAT JOB!
  • Seems like you contribute quite often. AMAZING!

Community-Driven Witness!

I am the first and only Steem Community-Driven Witness. Participate on Discord. Lets GROW TOGETHER!

mooncryption-utopian-witness-gif

Up-vote this comment to grow my power and help Open Source contributions like this one. Want to chat? Join me on Discord https://discord.gg/Pc8HG9x