18

Given a flat list of simple dictionaries

lst = [{'key1': 1}, {'key2': 2}, {'key3': 3}]

I'd like to find the dict that yields the minimum value evaluated using a method not detailed here. My first idea was to iterate the list to check dict by dict, but this fails:

for k, v in [x.items() for x in lst]:
    print(k, v)

results ValueError (as well as using a generator instead of the list):

for k, v in [x.items() for x in lst]:
ValueError: not enough values to unpack (expected 2, got 1)

However,

for x in lst:
    for k, v in x.items():
        print(k, v)

yields

key1 1
key2 2
key3 3

As expected. I assume all approaches work as intended (unless PEBKAC), but why doesn't it work using the list comprehension? Could someone enlighten me?

Edit: I use python 3 and I know items() yields a dict_view but I don't see why the logic doesn't work.

Alan Kavanagh
  • 9,425
  • 7
  • 41
  • 65
jake77
  • 1,892
  • 2
  • 15
  • 22
  • if you unpack `x.items()` it returns three lists with tuples of two (key & value) inside. If `x.items()` was a present, you'd have to unpack another layer of gift wrap paper. Try `x.items()[0]` in your list comprehension! – offeltoffel Sep 21 '17 at 13:59

4 Answers4

13

You're missing a level of iteration.

Normally, dicts have more than one key/value pair. dict.items() converts the entire dictionary into a sequence of tuples in the form (key, value). In your case, each dictionary has just one item, but the result of items() is still a tuple of tuples.

If you print the result of [x.items() for x in lst] you'll see that the result is a list of dict_view items; each one of those can itself be iterated.

Daniel Roseman
  • 588,541
  • 66
  • 880
  • 895
  • Right, as it is clear - for me as well - from the working solution. I guess I wanted to omit the [0] suggested by @offeltoffel, seems a bit like a magic number. I'll use the non-comprehansion version for readibility. – jake77 Sep 21 '17 at 14:08
  • Each tuple of k and v is wrapped as the only item in another list when trying to unpack your `lst`. What's the index of the only element in any list? It's `0`. No magic, just logic. I agree though that readability is more important than short codes, so you should implement whatever you feel most comfortable with. – offeltoffel Sep 21 '17 at 14:12
5

Instead of taking the first element of your dict_view you can add one level of indentation (as @DanielRoseman suggested) in your list comprehension:

for k, v in [(k, v) for x in lst for (k, v) in x.items()]:
  print(k, v)

yield

key1 1
key2 2
key3 3

as expected.

Kruupös
  • 5,097
  • 3
  • 27
  • 43
1

Try to unpack your lst step by step to understand how it is constructed.

>>> in: lst[0].items()
>>> out: [('key1', 1)]

Your result (that is equal to your x.items() expression in the list comprehension) is another list containing your tuple of k and v. The tuple is the element of index 0 in this list (and also the only element at all). So you need to go like

for k, v in [x.items()[0] for x in lst]:
    print(k, v)
offeltoffel
  • 2,691
  • 2
  • 21
  • 35
1

You can iterate over key, value pair of a dictionary like this:-

for k, v in dic.items():
    print(k, v)

what above code does is to first convert dictionary into a list of tuples which are then unpacked one by one while iterating into k, v just like:-

k, v = (k, v) # Tuple unpacking.

Now in your case, Consider this code:-

z = [x.items() for x in lst]
print(z)

Output is

[dict_items([('key1', 1)]), dict_items([('key2', 2)]), dict_items([('key3', 3)])] 

i.e a list of lists of tuple.

So, Let us rewrite your code as :-

for k, v in z:
    print(k, v)

Where z is a list of lists of tuple. At each iteration, it picks up a list(which contains only one element) from the parent list and then it tries:-

k, v = [(key, value)] # a list of only one tuple to be unpacked against two variables and hence the failure.

I hope this was helpful.

gautamaggarwal
  • 341
  • 2
  • 11