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

97

给定一个列表,有没有一种方法可以获取第一个非 None 值?如果有,使用 Pythonic 的方式是什么?

例如,我有:

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

在这种情况下,如果 a 是 None,则我想得到 b。 如果 a 和 b 都为 None,则我想得到 d。

目前,我正在执行类似于 (((a or b) or c) or d) 的操作,还有其他方法吗?

6个回答

186
你可以使用 next():
>>> a = [None, None, None, 1, 2, 3, 4, 5]
>>> next(item for item in a if item is not None)
1

如果列表中只包含None,则会抛出StopIteration异常。如果您想在这种情况下设置默认值,请执行以下操作:
>>> a = [None, None, None]
>>> next((item for item in a if item is not None), 'All are Nones')
All are Nones

6
我会在代码中添加 try: except StopIteration: 来处理仅包含 None 项的列表。但那是一个很好的答案。 - Germano
1
@Germano更新了答案,请查看。顺便说一句,Jon的答案应该被接受——他是第一个为仅包含None的列表提出解决方案的人。 - alecxe
1
我脑子一抽...感谢那些指出来的人...我认为这个答案更好地写出来了 ;) - Jon Clements
3
如果省略掉 "is not None",则在我的 Python 版本中,它会将 0 和 "" 视为与 None 相同。请注意,这不会改变原意。 - jcfollower
1
在评估函数时,与海象运算符结合使用效果很好: next((x for func in funcs if (x:= func(tree))), 'Default') - bjonen
显示剩余7条评论

35

我认为这是处理少量值时最简单的方法

firstVal = a or b or c or d

始终会返回第一个非“Falsey”值,在某些情况下有效(前提是您不希望返回任何可评估为false的值,正如@GrannyAching在下面所指出的那样)


9
如果存在任何被视为假值的数值,比如 0、''、[]、() 和 False,那么条件语句就不成立。 - Granny Aching
1
谢谢您的纠正,我忽略了那个(已更新帖子)。 - DannyMoshe
这很简洁明了。我想要第一个非空字符串。 - small mammal

19

first_true 是 Python 3 文档中 itertools 模块 中的一个配方:

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)

您可以选择实现后者,或导入 more_itertools 库,该库随 itertools 配送了一些已经做好的“配方”和更多功能:

> pip install more_itertools

使用:

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'

为什么要使用谓词?

"第一个非 None "的元素与"第一个 True "的元素不同,例如[None,None,0],其中0是第一个非 None,但它不是第一个True元素。谓词允许first_true可用,确保迭代器中任何首次出现的非 None 的假值项目仍然被返回(例如0False),而不是默认值。

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

太好了!可惜这是标准库的一部分。 - GLRoman
好的,这个配方是文档的一部分。你只需要自己实现它。 - pylang

10

当列表中的项目计算成本高时,例如在

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)

那么你可能想要避免重复计算并简化为:

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)

由于使用了生成器表达式,计算只会对前面的项目执行,直到产生一个真值为止。


1
在Python 3中,您可以使用map而不是生成器表达式。first_non_null = next(filter(bool, map(my_dict.get, my_list)), None) - Azeezah M
这确实更加优雅,我已经采纳了你的建议并更新了答案。 - Lars Blumberg

7
从以下内容进行改编(如果您愿意,可以将其压缩成一行):

从以下内容进行改编(如果您愿意,可以将其压缩成一行):

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

这个函数返回第一个非 None 的值,或者返回 None


4
首先要提到的是,在SQL中有这样一个函数,称为 coalesce。在Python中没有找到类似的功能,所以我自己编写了一个,使用了@alecxe的方法。请注意,保留HTML标签。
def first_not_none(*values):
    return next((v for v in values if v is not None), None)

在这种情况下确实非常有帮助:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接