Python list comprehension for dictionaries in dictionaries?

Question:

I just learned about list comprehension, which is a great fast way to get data in a single line of code. But something’s bugging me.

In my test I have this kind of dictionaries inside the list:

[{'y': 72, 'x': 94, 'fname': 'test1420'}, {'y': 72, 'x': 94, 'fname': 'test277'}]

The list comprehension s = [ r for r in list if r['x'] > 92 and r['x'] < 95 and r['y'] > 70 and r['y'] < 75 ] works perfectly on that (it is, in fact, the result of this line)

Anyway, I then realised I’m not really using a list in my other project, I’m using a dictionary. Like so:

{'test1420': {'y': '060', 'x': '070', 'fname': 'test1420'}}

That way I can simply edit my dictionary with var['test1420'] = ...

But list comprehensions don’t work on that!
And I can’t edit lists this way because you can’t assign an index like that.

Is there another way?

Asked By: skerit

||

Answers:

If dct is

{'test1420': {'y': '060', 'x': '070', 'fname': 'test1420'},
 'test277': {'y': 72, 'x': 94, 'fname': 'test277'},}

Perhaps you are looking for something like:

[ subdct for key,subdct in dct.iteritems() 
  if 92<subdct['x']<95 and 70<subdct['y']<75 ]

A little nicety is that Python allows you to chain inequalities:

92<dct[key]['x']<95

instead of

if r['x'] > 92 and r['x'] < 95

Note also that above I’ve written a list comprehension, so you get back a list (in this case, of dicts).

In Python3 there are such things as dict comprehensions as well:

{ n: n*n for n in range(5) } # dict comprehension
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

In Python2 the equivalent would be

dict( (n,n*n) for n in range(5) )

I’m not sure if you are looking for a list of dicts or a dict of dicts, but if you understand the examples above, it is easy to modify my answer to get what you want.

Answered By: unutbu

You can do this:

s = dict([ (k,r) for k,r in mydict.iteritems() if r['x'] > 92 and r['x'] < 95 and r['y'] > 70 and r['y'] < 75 ])

This takes a dict as you specified and returns a ‘filtered’ dict.

Answered By: adamk

You can get a list of the values of a dictionary d with d.values(). Your list comprehension should work using that, although I’m a little unclear what exactly you want the output to be.

Answered By: Rob Lourens

Sounds like you want something like:

my_dict = {'test1420': {'y': '060', 'x': '070', 'fname': 'test1420'},
           'test277' : {'y': '072', 'x': '094', 'fname': 'test277'}}


new_dict = dict((k,v) for k,v in my_dict.items() 
                    if 92 < int(v['x']) < 95 and 70 < int(v['y']) < 75)

Some notes on this code:

  1. I’m using a generator expression
    instead of a list comprehension
  2. Python lets you combine inequality
    tests as low < value < high
  3. The dict() constructor takes an iterable
    of key/value tuples to create a
    dictionary
Answered By: Kenan Banks

Is there another way?

Why not consider the use of some lightweight objects?

You can still use list comprehensions for gathering or filtering the objects, and gain a lot in clarity / extensibility.

>>> class Item(object):
...     def __init__(self, x, y, name):
...         self.x = x
...         self.y = y
...         self.name = name
... 
>>> list_items = []
>>> list_items.append(Item(x=70, y=60, name='test1420'))                        
>>> list_items.append(Item(x=94, y=72, name='test277'))                         
>>> items_matching = [item for item in list_items 
                      if 92 < item.x < 95 and 70 < item.y < 75]
>>> for item in items_matching:
...     print item.name
... 
test277
>>> first_item = items_matching[0]
>>> first_item.x += 50
>>> first_item.x
144
Answered By: ChristopheD

In Python 3 you can use dict comprehension which can be an even shorter solution:

{key_expression(item) : value_expression(item) for item in something if condition}
  1. In case you want to filter a dictionary as in the original question:

    mydict = {'test1': {'y':  60},'test2': {'y':  70},'test3': {'y':  80}}
    s = {k : r for k,r in mydict.items() if r['y'] < 75 }
    > {'test1': {'y': 60}, 'test2': {'y': 70}}
    
  2. Or we can even create something out of a list or range. E.g. if we want a dictionary with all odd square numbers:

    {i : i**2 for i in range(11) if i % 2 == 1}
    > {1: 1, 3: 9, 5: 25, 7: 49, 9: 81}
    
Answered By: Thomas R