Dict (Dictionary) Comprehensions in Python

In my never ending quest to grok Python and do more while writing less code, I came across dict (dictionary) comprehensions. See PEP 274.

Dict comprehensions are the dictionary analog to list comprehensions. This post provides a simple problem space, introduces list comprehensions with an example, then shows a similar pattern and example using dict comprehensions.

Problem space:

Let’s say we are using a dictionary to store some of our application settings. It is common for absolute paths to be used in the application configuration settings (Django projects mandate absolute paths in settings.py, for example). You either have to hardcode them, or somehow dynamically derive an absolute path.
One practice is to get the directory of the current file, this one from django-all-auth:

PROJECT_ROOT = os.path.normpath(os.path.dirname(os.path.abspath(__file__)))

For our simple example, I’m making the decision that our app data are going to have their own directory in the user’s home directory.

$HOME/myapp

List comprehensions:

For those not familiar with list comprehensions, it is a way to create a list from an iterable sequence. This sequence could be one or more source lists, iterables or generators. Here is an example of using a list comprehension to create the :

import os
# define our list of relative paths our application will need
rel_paths = [ 'myapp', 'myapp/log', 'myapp/data', 'myapp/media']

# Use a list comprehension to get the absolute paths
abs_paths = [os.path.join(os.environ['HOME'],path) for path in rel_paths]

So now when the app needs to create a new log file, it can reference abs_paths[1], and get something like

/home/youtheuser/myapp/log

Its important to note that the above does not assume that the actual application is executed out of $HOME/myapp, only the application data files are assumed to be there.

Now our list approach has a serious limitation: Lack of context. When you are deep in your code and you need to load an image file form your media folder, you’re going to have to remember that the media folder is in abs_path[3]. What happens if you or someone else inserts and element before ‘my app/media’ in your rel_paths list? Yes, abs_path[3] now doesn’t point to where it should and you have a bug. This is one of the many reasons why dictionaries are so useful.

rel_paths = {
'APP_HOME' : 'myapp',
'LOG_DIR' : 'myapp/log',
'DATA_DIR' : 'myapp/data',
'MEDIA_DIR' : 'myapp/media',
}

So now have both a mnemonic instead of an index for our setting and more robust code. As long as we don’t change the keys names, we can rearrange, insert new key-value pairs without impacting any other code. But we still have a relative path for each directory and need to get the absolute paths. Here’s how:

Dict comprehensions:

Dict comprehensions operate the same way, but use two variables, the key and its value, instead of the typically single value for list iteration.

abs_paths = { k: os.path.join(os.environ['HOME'], v) for (k,v) in rel_paths.iteritems() }

The only differences in syntax are:
1. We use braces instead of brackets. We are specifying the container type here
2. We specify the key:value pair in our expression
3. We identify the key and value variable names as a tuple following the for clause. in reality, there is no syntactic difference with this part of the comprehension syntax. Lists can have tuples to define the values used in the expression. Example:

a = [(1,2), (3,4), (5,6)]
b = [x*y for (x,y) in a]

Advertisements
Tagged with: , ,
Posted in Computing, Python