Invert a dictionary to a dictionary of lists
You are right, you should not use a list comprehension just for its side-effects. It is not very readable and only confuses the reader of the code.

Instead, just make it for loops. And put it into a function, so it is re-usable:

from collections import defaultdict

def reverse_dict_of_lists(d):
    reversed_dict = defaultdict(list)
    for key, values in d.items():
        for value in values:
            reversed_dict[value].append(key)
    return reversed_dict

if __name__ == "__main__":
    d = {"abc": [1, 2, 3], "cde": [3, 5, 7], "efg": [4, 2, 1, 7]}
    print(reverse_dict_of_lists(d))
    # defaultdict(&lt;type 'list'&gt;, {1: ['abc', 'efg'], 2: ['abc', 'efg'], 3: ['cde', 'abc'], 4: ['efg'], 5: ['cde'], 7: ['cde', 'efg']})


This might take a very small performance hit (it doesn't, see below), but this should be negligible compared to actually being able to understand what the code does.



Here are some timings on my machine. First I generate a list of some randomly filled dictionaries:

import random

def generate_dicts(dict_length, step=1):
    return [{i: [random.randrange(1, 100) for _ in range(j)]
             for i, j in zip(
                 [random.randrange(1, n_keys) for _ in range(n_keys)],
                 [random.randrange(1, 100) for _ in range(n_keys)])}
            for n_keys in range(2, dict_length + 2, step)]


This will generate dictionaries of the form {i: [...]}, where there are an increasing number of keys in the dictionary and also the length of the lists increases (but is random).

Plotting the time it takes to reverse the dictionary for dict_length = 100000, step = 1000:



And, with a slightly more realistic sample dict, which does not overwrite the keys and therefore shows the quadratic behavior of the double loop:

import numpy as np

def generate_dicts(n_keys_values):
    return ({i: [random.randrange(1, 100) for _ in range(random.randrange(1, n_keys))] for i in range(n_keys)} for n_keys in n_keys_values)

n_keys_values = np.logspace(1, 4.5, num=20, dtype=int)




This is on Ubuntu 17.04, Python 3.6.3, IPython 6.2.1, Intel i7-4710MQ (8) @ 3.500GHz.

You can see that the loop based approach is actually slightly faster in both cases :). The reason being, as Eric Duminil put it succinctly in the comments:


  A list comprehension might be faster than creating a list with a for loop. In [this] case, the list comprehension creates a useless list full of Nones. And this list isn't created at all with the for loop.