Hands-on: Python Fundamentals -- Dicts

Objectives:

Upon completion of this lesson, you should be able to:

  • Describe the characteristics of the dict in Python

  • Perform basic operations with dicts including creation, "querying", updates, and traversing

  • Get an idea in which situations dicts are and should be used

The dictionary data structure

  • In Python, a dictionary (or dict) is mapping between a set of indices (keys) and a set of values
  • The items in a dictionary are key-value pairs

The dictionary data structure

  • Keys can be any Python data type BUT
  • Because keys are used for indexing, they should be immutable
  • Values can be any Python data type
  • Values can be mutable or immutable

Creating a dictionary

  • There are a number of ways to create and fill a dictionary. E.g. you can create an empty one and keep assining new values
In [ ]:
# Create an empty dictionary
eng2sp = dict()
eng2sp = {}     # equivalent
print eng2sp
In [ ]:
eng2sp['one'] = 'uno'
print eng2sp
In [ ]:
eng2sp['two'] = 'dos'
print eng2sp

Creating a dictionary "hardcoded" way

  • In general, the order of items in a dictionary is unpredictable
In [ ]:
eng2sp = {'one': 'uno', 'two': 'dos', 'three': 'tres'}
print eng2sp

so never rely on any order of keys in the dictionary.


Note

If you need to maintain the order in which elements were added to dictionary, use "OrderedDict" (see

https://docs.python.org/2/library/collections.html#collections.OrderedDict)


Excercise

Try to create a dictionary with a key being

  • int
  • float
  • string
  • list of ints
  • some other dict

Which ones would work, and which ones would fail? What is the message?

Creating a dictionary from a list

You can create a dict from an iterable (e.g. list) which provides (key, value) pairs:

In [ ]:
eng2sp = dict([('one', 'uno'), ('two', 'dos'), ('three', 'tres')])
print eng2sp

Excercise

Quite frequently you might have already such two lists which contain your keys and values. Then you could easily create a necessary list of pairs from them up to produce a dictionary -- which function will you use? Do it:

In [ ]:
eng = ['one', 'two', 'three']
sp = ['uno', 'dos', 'tres']
dict(TODO)

Dictionary comprehension

Similarly to list comprehensions, there are dict-comprehensions which allow for faster, flexible, and concise in code dynamic creation of lists. E.g. if we wanted a dictionary only for those english words with 'e' in them:

In [ ]:
{e:s for e,s in zip(eng, sp) if 'e' in e}

Dictionary indexing

  • Dictionaries are indexed by keys, not by a positional index as lists (but your keys could be integers as well)
In [ ]:
eng2sp['three']
  • If the index is not a key in the dictionary, Python raises an exception
In [ ]:
eng2sp['five']

Dictionary indexing

  • You can check for the existence of a key using in operator (known also to e.g. lists) so you can avoid that error:
In [ ]:
if 'five' in eng2sp:
   print eng2sp['five']

or use .get method and provide alternative default:

In [ ]:
print eng2sp.get('five', "sorry -- no spanish")

The in operator

  • Note that the in operator works a little bit differently for dictionaries than for other sequences
  • For offset indexed sequences (strings, lists, tuples), x in y checks to see whether x is an item in the sequence
In [ ]:
6 in [4,5,6,7]
True
  • For dictionaries, x in y checks to see whether x is a key in the dictionary
In [ ]:
'two' in {'one':'uno', 'two':'dos', 'three':'tres'}
In [ ]:
'dos' in {'one':'uno', 'two':'dos', 'three':'tres'}

Deleting items

In [ ]:
# Let's add some new value
eng2sp['five'] = 'cinco'
print eng2sp
In [ ]:
del eng2sp['five']
print eng2sp

Or, if you are interested to obtain the value and remove it from the dictionary -- use .pop:

In [ ]:
eng2sp['five'] = 'cinco'
five_sp = eng2sp.pop('five')
print five_sp
print eng2sp

Keys and values. "(key, value)" is an "item"

  • The keys method returns a list of the keys in a dictionary
In [ ]:
print eng2sp.keys()
  • The values method returns a list of the values
In [ ]:
print eng2sp.values()
  • The items method returns a list of tuple pairs of the key-value pairs in a dictionary
In [ ]:
print eng2sp.items()
  • So if you need to check either your dictionary contains a specific value, check among its values:
In [ ]:
'dos' in {'one':'uno', 'two':'dos', 'three':'tres'}.values()

Example: histogram.py

Let's imagine we need to count appearances of every unique element of a sequence, e.g. of a string:

In [ ]:
def histogram(seq):
    d = dict()
    for element in seq:
        if element not in d:
            d[element] = 1
        else:
            d[element] += 1
    return d

h = histogram('brontosaurus')
print h

And lets create a helper function to print such a histogram:

In [ ]:
def print_hist(hist):
    for key in hist:
        print key, hist[key]

h = histogram('brontosaurus')
print_hist(h)

Now that we have learned so much, let's change the print_hist function to use (key,value) pairs:

In [ ]:
def print_hist(hist):
    for key, value in hist:
        print key, value

h = histogram('brontosaurus')
print_hist(h)

Question: What happened?

Excercises:

  1. Fix print_hist
  2. Modify histogram to make use of dict.get and make implementation simpler/more concise

Sorting the keys

  • Do you remember that dicts do not preserve the order of items? So what if we wanted to get the histogram in order?
In [ ]:
def print_hist(hist):
    for key in sorted(hist.keys()):
        print key, hist[key]

h = histogram('brontosaurus')
print_hist(h)

MiniExcercise

Use above example code, but adjust it to make use of a .sort() method of a list you obtain from hist.keys() instead of using sorted function


Excercise

Develop a function invert_dict which for a dictionary would invert keys and values, i.e. would create inverse mapping. Here is a code "stub" and test cases:

In [ ]:
def invert_dict(d):
    # TODO magic, try to come up with 1 line solution

sp2eng = invert_dict(eng2sp)
assert(sp2eng == {'dos': 'two', 'tres': 'three', 'uno': 'one'})

Keyword arguments to the function are passed as a dict

Similarly to as tuple can absorb all positional arguments to a function (*args), dict can absorb all keyword arguments:

  • A parameter name that begins with ** gathers all the arguments into a dictionary

  • This allows functions to take a variable number of keyword arguments

You might frequently see a pattern such as

In [ ]:
# some function, this one is just for example
def func(x, param=0):
    print "got x=%s param=%s" % (x, param)
    
# another function which passes majority or all arguments
# into func
def func2(a, *args, **kwargs):
    print a, args, kwargs
    func(*args, **kwargs)


func2(1, 'three')
func2(1, 'four', param="123")

Question

What disadvantage(s) of such a construct?