Dict

Dictionaries can be thought of as a collection of key-value pairs or a named list of items. It used to be unordered, but Python now ensures that the insertion order is maintained. See this tutorial for a more detailed discussion on dict usage.

Initialization and accessing elements

A dict data type is declared within {} characters and each item requires two values — an immutable data type for keys, followed by a : character and finally a value of any data type. The elements are separated by a comma character, just like the other container types.

To access an element, the syntax is dict_variable[key]. Retrieving an item takes a constant amount of time, irrespective of the size of the dict (see Hashtables for details). Dictionaries are mutable, so you can change an item's value, add items, remove items, etc.

>>> marks = {'Rahul': 86, 'Ravi': 92, 'Rohit': 75, 'Rajan': 79}

>>> marks['Rohit']
75
>>> marks['Rahul'] += 5
>>> marks['Ram'] = 67 
>>> del marks['Rohit']
# note that the insertion order is maintained
>>> marks
{'Rahul': 91, 'Ravi': 92, 'Rajan': 79, 'Ram': 67}

Here's an example with list and tuple keys.

>>> list_key = {[1, 2]: 42}
Traceback (most recent call last):
  File "<python-input-0>", line 1, in <module>
    list_key = {[1, 2]: 42}
               ^^^^^^^^^^^^
TypeError: unhashable type: 'list'

>>> items = {('car', 2): 'honda', ('car', 5): 'tesla', ('bike', 10): 'hero'}
>>> items[('bike', 10)]
'hero'

You can also use the dict() function for initialization in various ways. If all the keys are of str data type, you can use the same syntax as keyword arguments seen earlier with function definitions. You can also pass a container type having two values per element, such as a list of tuples as shown below.

>>> marks = dict(Rahul=86, Ravi=92, Rohit=75, Rajan=79)
>>> marks
{'Rahul': 86, 'Ravi': 92, 'Rohit': 75, 'Rajan': 79}

>>> items = [('jeep', 20), ('car', 3), ('cycle', 5)]
>>> dict(items)
{'jeep': 20, 'car': 3, 'cycle': 5}

Another way to initialize is to use the fromkeys() method that accepts an iterable and an optional value (default is None). The same value will be assigned to all the keys, so be careful if you want to use a mutable object, since the same reference will be used as well.

>>> colors = ('red', 'blue', 'green')
>>> dict.fromkeys(colors)
{'red': None, 'blue': None, 'green': None}
>>> dict.fromkeys(colors, 255)
{'red': 255, 'blue': 255, 'green': 255}

get and setdefault

If you try to access a dict key that doesn't exist, you'll get a KeyError exception. If you do not want an exception to occur, you can use the get() method. By default it'll return a None value for keys that do not exist, which you can change by providing a default value as the second argument.

>>> marks = dict(Rahul=86, Ravi=92, Rohit=75, Rajan=79)

>>> marks['Ron']
Traceback (most recent call last):
  File "<python-input-1>", line 1, in <module>
    marks['Ron']
    ~~~~~^^^^^^^
KeyError: 'Ron'

>>> marks.get('Ravi')
92
>>> value = marks.get('Ron')
>>> print(value)
None
>>> marks.get('Ron', 0)
0

Here's a more practical example:

>>> vehicles = ['car', 'jeep', 'car', 'bike', 'bus', 'car', 'bike']
>>> hist = {}
>>> for v in vehicles:
...     hist[v] = hist.get(v, 0) + 1
... 
>>> hist
{'car': 3, 'jeep': 1, 'bike': 2, 'bus': 1}

Using the get() method will not automatically add keys that do not exist yet to the dict object. You can use the setdefault() method, which behaves similarly to get() except that keys will get created if not found. See also docs.python: collections.defaultdict.

>>> marks = dict(Rahul=86, Ravi=92, Rohit=75, Rajan=79)

>>> marks.get('Ram', 40)
40
>>> marks
{'Rahul': 86, 'Ravi': 92, 'Rohit': 75, 'Rajan': 79}

>>> marks.setdefault('Ram', 40)
40
>>> marks
{'Rahul': 86, 'Ravi': 92, 'Rohit': 75, 'Rajan': 79, 'Ram': 40}

Iteration

The default for loop over a dict object will give you a key for each iteration.

>>> fruits = dict(banana=12, papaya=5, mango=10, fig=100)

>>> for k in fruits:
...     print(f'{k}:{fruits[k]}')
... 
banana:12
papaya:5
mango:10
fig:100

# similarly, you'll get only the keys if you apply list(), tuple() or set()
>>> list(fruits)
['banana', 'papaya', 'mango', 'fig']

As an exercise,

  • given fruits dictionary as defined in the above code snippet, what do you think will happen when you use a, *b, c = fruits?
  • given nums = [1, 4, 6, 22, 3, 5, 4, 3, 6, 2, 1, 51, 3, 1], keep only the first occurrences of a value from this list without changing the order of elements. You can do it with the dict features presented so far. [1, 4, 6, 22, 3, 5, 2, 51] should be the output.

Dict methods and operations

The in operator checks if a key is present in the given dictionary. The keys() method returns all the keys and the values() method returns all the values. These methods return a custom set-like object, but with the insertion order maintained.

>>> marks = dict(Rahul=86, Ravi=92, Rohit=75, Rajan=79)

>>> 'Ravi' in marks
True
>>> 'Ram' in marks
False

>>> marks.keys()
dict_keys(['Rahul', 'Ravi', 'Rohit', 'Rajan'])
>>> marks.values()
dict_values([86, 92, 75, 79])

The items() method can be used to get a key-value tuple for each iteration.

>>> fruits = dict(banana=12, papaya=5, mango=10, fig=100)

# set-like object
>>> fruits.items()
dict_items([('banana', 12), ('papaya', 5), ('mango', 10), ('fig', 100)])

>>> for fruit, qty in fruits.items():
...     print(f'{fruit}\t: {qty}')
... 
banana  : 12
papaya  : 5
mango   : 10
fig     : 100

The del statement example seen earlier removes the given key without returning the value associated with it. You can use the pop() method to get the value as well. The popitem() method removes the last added item and returns the key-value pair as a tuple.

>>> marks = dict(Rahul=86, Ravi=92, Rohit=75, Rajan=79)

>>> marks.pop('Ravi')
92
>>> marks
{'Rahul': 86, 'Rohit': 75, 'Rajan': 79}

>>> marks.popitem()
('Rajan', 79)
>>> marks
{'Rahul': 86, 'Rohit': 75}

The update() method allows you to add/update items from another dictionary or a container with key-value pair elements.

>>> marks = dict(Rahul=86, Ravi=92, Rohit=75, Rajan=79)
>>> marks.update(dict(Jo=89, Joe=75, Ravi=100))
# note that 'Ravi' has '100' as the updated value
>>> marks
{'Rahul': 86, 'Ravi': 100, 'Rohit': 75, 'Rajan': 79, 'Jo': 89, 'Joe': 75}

>>> fruits = dict(papaya=5, mango=10, fig=100)
>>> fruits.update([('tomato', 3), ('banana', 10)])
>>> fruits
{'papaya': 5, 'mango': 10, 'fig': 100, 'tomato': 3, 'banana': 10}

The | operator is similar to the update() method, except that you get a new dict object instead of in-place modification.

>>> d1 = {'banana': 12, 'papaya': 5, 'mango': 20}
>>> d2 = {'mango': 10, 'fig': 100}

# before the introduction of the | operator,
# you had to use unpacking, i.e. {**d1, **d2}
>>> d1 | d2
{'banana': 12, 'papaya': 5, 'mango': 10, 'fig': 100}

Arbitrary keyword arguments

To accept an arbitrary number of keyword arguments, use **var_name in the function definition. This has to be declared the last, after all the other types of arguments. Idiomatically, **kwargs is used as the variable name. See stackoverflow: Decorators demystified for a practical example.

>>> def many(**kwargs):
...     print(f'{kwargs = }')
... 
>>> many()
kwargs = {}
>>> many(num=5)
kwargs = {'num': 5}
>>> many(car=5, jeep=25)
kwargs = {'car': 5, 'jeep': 25}

Turning it around, when you have a function defined with keyword arguments, you can unpack a dictionary while calling the function.

>>> def greeting(phrase='hello', style='='):
...     print(f'{phrase:{style}^{len(phrase)+6}}')
... 
>>> greeting()
===hello===
>>> d = {'style': '-', 'phrase': 'have a nice day'}
>>> greeting(**d)
---have a nice day---