0

There is a dict (say d). dict.get(key, None) returns None if key doesn't exist in d.

How do I get the first value (i.e., d[key] is not None) from a list of keys (some of them might not exist in d)?

This post, Pythonic way to avoid “if x: return x” statements, provides a concrete way.

for d in list_dicts:
    for key in keys:
        if key in d:
            print(d[key])
            break

I use xor operator to acheive it in one line, as demonstrated in,

# a list of dicts
list_dicts = [  {'level0' : (1, 2), 'col': '#ff310021'},
                {'level1' : (3, 4), 'col': '#ff310011'},
                {'level2' : (5, 6), 'col': '#ff312221'}]

# loop over the list of dicts dicts, extract the tuple value whose key is like level*
for d in list_dicts:
    t = d.get('level0', None) or d.get('level1', None) or d.get('level2', None)
    col = d['col']

    do_something(t, col)

It works. In this way, I just simply list all options (level0 ~ level3). Is there a better way for a lot of keys (say, from level0 to level100), like list comprehensions?

Community
  • 1
  • 1
SparkAndShine
  • 17,001
  • 22
  • 90
  • 134
  • @user3100115, I edited the question and referred the post you mentioned. I believe it is not duplicate. Pls check it again. – SparkAndShine May 20 '16 at 20:45
  • This question is basically a particular case of the original (duplicate) question, where the conditions are `d.get('level0', None)`, `d.get('level1', None)` and so on instead of `check_size()`, `check_color()`, etc. – Cristian Ciupitu May 21 '16 at 14:03

5 Answers5

4

This line:

x, y = d.get('level0', None) or d.get('level1', None) or d.get('level2', None)

Is basically mapping a list of ['level0', 'level1', 'level2'] to d.get (None is already the default value; there's no need to explicitly state it in this case). Next, you want to choose the one that doesn't map to None, which is basically a filter. You can use the map() and filter() built-in functions (which are lazy generator-like objects in Python 3) and call next() to get the first match:

list_dicts = [  {'level0' : (1, 2), 'col': '#ff310021'},
                {'level1' : (3, 4), 'col': '#ff310011'},
                {'level2' : (5, 6), 'col': '#ff312221'}]
>>> l = 'level0', 'level1', 'level2'
>>> for d in list_dicts:
...     print(next(filter(None, map(d.get, l))))
...
(1, 2)
(3, 4)
(5, 6) 
TigerhawkT3
  • 48,464
  • 6
  • 60
  • 97
3

There's no convenient builtin, but you could implement it easily enough:

def getfirst(d, keys):
    for key in keys:
        if key in d:
            return d[key]
    return None
user2357112
  • 260,549
  • 28
  • 431
  • 505
1

I would use next with a comprehension:

# build list of keys
levels = [ 'level' + str(i) for i in range(3) ]

for d in list_dicts:
  level_key = next(k for k in levels if d.get(k))
  level = d[level_key]
Mark Reed
  • 91,912
  • 16
  • 138
  • 175
1

Should work on all Pythons:

# a list of dicts
list_dicts = [{'level0': (1, 2), 'col': '#ff310021'},
              {'level1': (3, 4), 'col': '#ff310011'},
              {'level2': (5, 6), 'col': '#ff312221'}]

# Prioritized (ordered) list of keys [level0, level99]
KEYS = ['level{}'.format(i) for i in range(100)]


# loop over the list of dicts dicts, extract the tuple value whose key is
# like level*
for d in list_dicts:
    try:
        k = next(k for k in KEYS if k in d)
        t = d[k]
        col = d['col']

        do_something(t, col)
    except StopIteration:
        pass
totoro
  • 2,469
  • 2
  • 19
  • 23
1

Just as a novelty item, here is a version that first computes a getter using functional composition.

if 'reduce' not in globals():
    from functools import reduce

list_dicts = [  {'level0' : (1, 2), 'col': '#ff310021'},
                {'level1' : (3, 4), 'col': '#ff310011'},
                {'level2' : (5, 6), 'col': '#ff312221'}]


levels = list(map('level{}'.format, range(3)))
getter = reduce(lambda f, key: lambda dct: dct.get(key, f(dct)), reversed(levels), lambda _: None)

print(list(map(getter, list_dicts)))

# [(1, 2), (3, 4), (5, 6)]
hilberts_drinking_problem
  • 11,322
  • 3
  • 22
  • 51