Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python how to use defaultdict fromkeys to generate a dictionary with predefined keys and empty lists

Tags:

python

Here is the code:

from collections import defaultdict

result = defaultdict.fromkeys(['a','b','c'], list)
result['a'].append(1)

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-44-6c01c8d56a42> in <module>()
----> 1 result['a'].append('1')

TypeError: descriptor 'append' requires a 'list' object but received a 'str'

I don't understand the error message, what went wrong and how to fix it?

like image 987
Cheng Avatar asked May 04 '17 01:05

Cheng


2 Answers

The .fromkeys method is primarily used to set a dict to a single same default value:

>>> {}.fromkeys(['a','b','c'])
{'a': None, 'c': None, 'b': None}

It does not call a function for each key (which is what list or [] is).

Default dict needs a 'factory function' to be a default dict:

>>> from collections import defaultdict
>>> result=defaultdict(list)
>>> result
defaultdict(<type 'list'>, {})

The factory function (in this case, list) is called any time a missing key is added to a defaultdict to form the default value.

So to set three lists with keys 'a','b','c' you would do:

>>> for e in ('a','b','c'):
...    result[e]       # 'e' is not in the dict, so it is added 
                       #    and a new list is the value 
>>> result
defaultdict(<type 'list'>, {'a': [], 'c': [], 'b': []})

Or, you can use the .update method as Raymond Hettinger points out:

>>> result=defaultdict(list)
>>> result.update((k,[]) for k in 'abc')
>>> result
defaultdict(<type 'list'>, {'a': [], 'c': [], 'b': []})  

Or, as ivan_pozdeev points out in comments, you can do:

>>> di=defaultdict(list,{k:[] for k in 'abc'})
>>> di
defaultdict(<type 'list'>, {'a': [], 'c': [], 'b': []})  

Or you can use a regular Python dict with a dict comprehension to get the same thing -- no defaultdict required -- if you only need or want those three keys with unique lists as their values:

>>> di={k:[] for k in 'abc'}
>>> di
{'a': [], 'c': [], 'b': []}

And those are separate lists for each key:

>>> di['a'].append(1)
>>> di
{'a': [1], 'c': [], 'b': []}

A common mistake (which I have made ;-0) is to use something like .fromkeys and get the same list referred to by multiple keys:

>>> di2={}.fromkeys(['a','b','c'], [])
>>> di2
{'a': [], 'c': [], 'b': []}      # looks ok
>>> di2['a'].append('WRONG!')
>>> di2
{'a': ['WRONG!'], 'c': ['WRONG!'], 'b': ['WRONG!']}

Happens because the same single list is referred to by all the keys.

like image 177
dawg Avatar answered Oct 22 '22 05:10

dawg


Unfortunately, fromkeys() is for setting the same value over and over again. It isn't helpful when you need distinct lists.

So, I would tackle the problem like this:

>>> keys = ['barry', 'tim', 'fredrik', 'alex']

>>> d = defaultdict(list)
>>> d.update((k, []) for k in keys)
>>> d
defaultdict(<class 'list'>, {'barry': [], 'tim': [], 'fredrik': [], 'alex': []})
like image 34
Raymond Hettinger Avatar answered Oct 22 '22 07:10

Raymond Hettinger