15

First, I'm new to Python, so I apologize if I've overlooked something, but I would like to use dict.fromkeys (or something similar) to create a dictionary of lists, the keys of which are provided in another list. I'm performing some timing tests and I'd like for the key to be the input variable and the list to contain the times for the runs:

def benchmark(input):
    ...
    return time_taken

runs = 10
inputs = (1, 2, 3, 5, 8, 13, 21, 34, 55)
results = dict.fromkeys(inputs, [])

for run in range(0, runs):
    for i in inputs:
        results[i].append(benchmark(i))

The problem I'm having is that all the keys in the dictionary appear to share the same list, and each run simply appends to it. Is there any way to generate a unique empty list for each key using fromkeys? If not, is there another way to do this without generating the resulting dictionary by hand?

Kyle Cronin
  • 77,653
  • 43
  • 148
  • 164

3 Answers3

14

The problem is that in

results = dict.fromkeys(inputs, [])

[] is evaluated only once, right there.

I'd rewrite this code like that:

runs = 10
inputs = (1, 2, 3, 5, 8, 13, 21, 34, 55)
results = {}

for run in range(runs):
    for i in inputs:
        results.setdefault(i,[]).append(benchmark(i))

Other option is:

runs = 10
inputs = (1, 2, 3, 5, 8, 13, 21, 34, 55)
results = dict([(i,[]) for i in inputs])

for run in range(runs):
    for i in inputs:
        results[i].append(benchmark(i))
vartec
  • 131,205
  • 36
  • 218
  • 244
  • Awesome, that works nicely! Thanks! (though I wish it were possible to generate the empty lists before I had to use them) – Kyle Cronin Mar 17 '09 at 15:20
  • It is not possible. Once you call [] or list() an object is created and variable bound. Check out for example this x = [[]]*10; x[0].append('test'); print x – vartec Mar 17 '09 at 15:24
  • Ok, there you have alternative with all list instantiated. – vartec Mar 17 '09 at 15:29
  • Thanks for the other option - I suspected that I could use a comprehension, but I didn't find any info on dictionary comprehensions; I hadn't considered to write a list comprehension and turn it into a dictionary! – Kyle Cronin Mar 17 '09 at 15:38
  • Well, yeah, "dictionary comprehension" basically would be something derived form dict([(k,v) for k,v in dictionary.items()]) – vartec Mar 17 '09 at 15:45
  • 1
    There are actually dictionary (and set) comprehensions in python3. The syntax is "{x:[] for x in inputs}" – Brian Mar 17 '09 at 16:26
  • 2
    `[]` can be dropped: `results = dict((key, []) for key in inputs)` – jfs Mar 17 '09 at 21:00
12

Check out defaultdict (requires Python 2.5 or greater).

from collections import defaultdict

def benchmark(input):
    ...
    return time_taken

runs = 10
inputs = (1, 2, 3, 5, 8, 13, 21, 34, 55)
results = defaultdict(list) # Creates a dict where the default value for any key is an empty list

for run in range(0, runs):
    for i in inputs:
        results[i].append(benchmark(i))
Hank Gay
  • 70,339
  • 36
  • 160
  • 222
  • That works well too - I just wish that it was a "real" dictionary, not a class pretending to be one. – Kyle Cronin Mar 17 '09 at 15:23
  • To be fair, it's a subclass with very minimal changes, so "pretending to be one" seems a bit strong. – Hank Gay Mar 17 '09 at 15:28
  • +1 It's the way to do it if you're sure you won't have to use your code with Python < 2.5. (recently I've look up hosting offers, and there are a lot still using Python 2.4). – vartec Mar 17 '09 at 15:33
  • @vartec Good point - I'll add a disclaimer to the body of the answer itself. – Hank Gay Mar 17 '09 at 15:36
  • defaultdict is useful in so many cases. I first heard about it in Peter Norvig's, "How to write a spelling corrector": http://www.norvig.com/spell-correct.html – hughdbrown Mar 19 '09 at 12:47
2

You can also do this if you don't want to learn anything new (although I recommend you do!) I'm curious as to which method is faster?

results = dict.fromkeys(inputs)

for run in range(0, runs):
    for i in inputs:
        if not results[i]:
            results[i] = []
        results[i].append(benchmark(i))
Jason Coon
  • 17,601
  • 10
  • 42
  • 50