从列表中获取第一个非 Nothing 值

给定一个列表,是否有方法获得第一个非 -Nothing 值?如果是这样,那么怎样才能做到这一点呢?

例如,我有:

  • a = objA.addreses.country.code
  • b = objB.country.code
  • c = None
  • d = 'CA'

在这种情况下,如果 a 是 Nothing,那么我想得到 b。如果 a 和 b 都是无,我想得到 d。

目前我正在沿着 (((a or b) or c) or d)的路线做一些事情,还有其他的方法吗?

56254 次浏览

You can use next():

>>> a = [None, None, None, 1, 2, 3, 4, 5]
>>> next(item for item in a if item is not None)
1

If the list contains only Nones, it will throw StopIteration exception. If you want to have a default value in this case, do this:

>>> a = [None, None, None]
>>> next((item for item in a if item is not None), 'All are Nones')
All are Nones

Adapt from the following (you could one-liner it if you wanted):

values = (a, b, c, d)
not_None = (el for el in values if el is not None)
value = next(not_None, None)

This takes the first non None value, or returns None instead.

first_true is an itertools recipe found in the Python 3 docs:

def first_true(iterable, default=False, pred=None):
"""Returns the first true value in the iterable.


If no true value is found, returns *default*


If *pred* is not None, returns the first item
for which pred(item) is true.


"""
# first_true([a,b,c], x) --> a or b or c or x
# first_true([a,b], x, f) --> a if f(a) else b if f(b) else x
return next(filter(pred, iterable), default)

One may choose to implement the latter recipe or import more_itertools, a library that ships with itertools recipes and more:

> pip install more_itertools

Use:

import more_itertools as mit


a = [None, None, None, 1, 2, 3, 4, 5]
mit.first_true(a, pred=lambda x: x is not None)
# 1


a = [None, None, None]
mit.first_true(a, default="All are None", pred=lambda x: x is not None)
# 'All are None'

Why use the predicate?

"First non-None" item is not the same as "first True" item, e.g. [None, None, 0] where 0 is the first non-None, but it is not the first True item. The predicate allows first_true to be useable, ensuring any first seen, non-None, falsey item in the iterable is still returned (e.g. 0, False) instead of the default.

a = [None, None, None, False]
mit.first_true(a, default="All are None", pred=lambda x: x is not None)
# 'False'

I think this is the simplest way when dealing with a small set of values:

firstVal = a or b or c or d

Will always return the first non "Falsey" value which works in some cases (given you dont expect any values which could evaluate to false as @GrannyAching points out below)

When the items in your list are expensive to calculate such as in

first_non_null = next((calculate(x) for x in my_list if calculate(x)), None)


# or, when receiving possibly None-values from a dictionary for each list item:


first_non_null = next((my_dict[x] for x in my_list if my_dict.get(x)), None)

then you might want to avoid the repetitive calculation and simplify to:

first_non_null = next(filter(bool, map(calculate, my_list)), None)


# or:


first_non_null = next(filter(bool, map(my_dict.get, my_list)), None)

Thanks to the usage of a generator expression, the calculations are only executed for the first items until a truthy value is generated.

First of all want to mention that such function exists in SQL and is called coalesce. Found no such thing in Python so made up my own one, using the recipe of @alecxe.

def first_not_none(*values):
return next((v for v in values if v is not None), None)

Really helps in cases like this:

attr = 'title'
document[attr] = first_not_none(cli_args.get(attr), document_item.get(attr),
defaults_item.get(attr), '')