1

I have a list of dictionaries that do not necessarily have the same keys. I am trying to make a new list of the values contained in each dictionary that has a specific key.

E.g.

dict1 = {'key1': 1, 'key2': 2, 'key4': 4}
dict2 = {'key2': 2, 'key3': 3}
dict3 = {'key1': 1, 'key2': 2, 'key3': 3}

dict_list = [dict1, dict2, dict3]

Now I'm trying to create a list from dict_list as follows:

key2vals = [dict['key2'] for dict in dict_list]
print(key2vals)
   [2, 2, 2]

That works well, since 'key2' is contained in every dictionary in the list. However, when I try a key that does not appear in every dictionary, I get a fault. E.g. 'key1':

>>> key1vals = [dict['key1'] for dict in dict_list]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <listcomp>
KeyError: 'key1'

Is there a short and straightforward way that I can do something like the following, which does not work?

# This does not work
key1vals = [dict['key1'] if 'key1' in dict for dict in dict_list]

What is the most Pythonic way to accomplish this?

ANSWER: The order of the for and if statements should have been reversed:

key1vals = [dict['key1'] for dict in dict_list if 'key1' in dict ]

Also, with AbrahamB and DJV's suggestions, I was able to add a default value to those dictionaries without the search key:

[dict.get('key1', 0) for dict in dict_list] 
Rusty Lemur
  • 1,697
  • 1
  • 21
  • 54

4 Answers4

2

Absolutely. The dict has a "get" method, which you can use.

For example

a = {}
a.get('this-does-not-exist')

That will return a None type.

Now you can get fancier, such as returning a default value if nothing is found:

a = {}
a.get('this-does-not-exist', [])

which will return an empty list [].

AbrahamB
  • 1,348
  • 8
  • 13
  • Can you use the a.get('key', 'defaultValue') format in the original example problem? I'm not quite following how to use it in the bigger picture. – Rusty Lemur Oct 26 '16 at 23:30
  • DJV just provided the answer in another comment: [d.get('key1', 0) for d in dict_list] – Rusty Lemur Oct 26 '16 at 23:32
2
dict1 = {'key1': 1, 'key2': 2, 'key4': 4}
dict2 = {'key2': 2, 'key3': 3}
dict3 = {'key1': 1, 'key2': 2, 'key3': 3}

dict_list = [dict1, dict2, dict3]

keys = ['key1', 'key2', 'key3', 'key4', 'key5']
for key in keys:
    values = [d[key] for d in dict_list if key in d]
    print(values)
[1, 1]
[2, 2, 2]
[3, 3]
[4]
[]

But the same item lookup is done twice here: key in d and d[key]. You can avoid it:

    values = list(filter(None, (d.get(key) for d in dict_list)))

or

    values = [i for i in (d.get(key) for d in dict_list) if i is not None]

Added: As you mentioned in comments, you actually need 0 if key is not in dict. Just use get method with default value:

    values = [d.get(key, 0) for d in dict_list]
[1, 0, 1]
[2, 2, 2]
[0, 3, 3]
[4, 0, 0]
[0, 0, 0]
Mikhail M.
  • 5,588
  • 3
  • 23
  • 31
1

You were pretty close:

key1vals = [d['key1'] for d in dict_list if 'key1' in d]

To get all keys' values:

all_keys = {key for d in dict_list for key in d}

result_dict = {}
for key in all_keys:
    result_dict[key] = [d[key] for d in dict_list if key in d]

print(result_dict)

Which prints:

{'key1': [1, 1], 'key4': [4], 'key3': [3, 3], 'key2': [2, 2, 2]}
blacksite
  • 12,086
  • 10
  • 64
  • 109
0

Thanks for the suggestions! I found another solution which simply reversed the "for" and "if" statements in my previous attempt:

>>> key1vals = [dict['key1'] for dict in dict_list if 'key1' in dict]
>>> key1vals
[1, 1]
>>> 

Would the filter or "get" methods be preferred to this?

Rusty Lemur
  • 1,697
  • 1
  • 21
  • 54
  • I would suggest using the get since it is cleaner. You're already calling the key in dict['key1'], so this would be shorter as just dict.get('key1') – AbrahamB Oct 26 '16 at 23:19
  • Either way. Yeah, it's shorter, but I think the `if 'key1' in d` language may be a bit more Pythonic and easier to read. That approach literally checks whether the dictionary even has a key `'key1'`. You can't go wrong either way. – blacksite Oct 26 '16 at 23:24
  • Can I add an "else" statement, such as >>>key1vals = [dict['key1'] for dict in dict_list if 'key1' in dict else 0]. Then I would want key1vals to contain [1, 0, 1]. – Rusty Lemur Oct 26 '16 at 23:27
  • `[d.get('key1', 0) for d in dict_list]` – Mikhail M. Oct 26 '16 at 23:29