Python习语,表示返回第一项或不返回任何项

我相信还有一种更简单的方法,只是我没有想到。

我调用了一系列返回列表的方法。列表可能是空的。如果列表非空,我想返回第一项;否则,我想返回None。这段代码:

1
2
3
my_list = get_list()
if len(my_list) > 0: return my_list[0]
return None

在我看来,应该有一个简单的单行成语来做这件事,但是我怎么也想不出来。是吗?

编辑:

我在这里寻找一个单行表达式的原因不是因为我喜欢非常简洁的代码,而是因为我必须写很多这样的代码:

1
2
3
4
5
6
7
8
x = get_first_list()
if x:
    # do something with x[0]
    # inevitably forget the [0] part, and have a bug to fix
y = get_second_list()
if y:
    # do something with y[0]
    # inevitably forget the [0] part AGAIN, and have another bug to fix

我想做的事情当然可以用一个函数来完成(很可能会):

1
2
3
4
5
6
7
8
9
def first_item(list_or_none):
    if list_or_none: return list_or_none[0]

x = first_item(get_first_list())
if x:
    # do something with x
y = first_item(get_second_list())
if y:
    # do something with y

我提出这个问题是因为我经常对Python中的简单表达式所能做的事情感到惊讶,我认为如果有一个简单的表达式可以做到这一点,那么编写一个函数就是一件愚蠢的事情。但是看到这些答案,函数似乎是简单的解。


最好的方法是:

1
2
a = get_list()
return a[0] if a else None

你也可以用一行来写,但是程序员很难读懂:

1
return (get_list()[:1] or [None])[0]


Python 2.6 +

1
next(iter(your_list), None)

如果your_list可以是None:

1
next(iter(your_list or []), None)

Python 2.4

1
2
3
4
5
def get_first(iterable, default=None):
    if iterable:
        for item in iterable:
            return item
    return default

例子:

1
2
3
4
5
6
x = get_first(get_first_list())
if x:
    ...
y = get_first(get_second_list())
if y:
    ...

另一个选项是内联上述函数:

1
2
3
4
5
6
for x in get_first_list() or []:
    # process x
    break # process at most one item
for y in get_second_list() or []:
    # process y
    break

为了避免break,你可以这样写:

1
2
3
4
for x in yield_first(get_first_list()):
    x # process x
for y in yield_first(get_second_list()):
    y # process y

地点:

1
2
3
4
def yield_first(iterable):
    for item in iterable or []:
        yield item
        return


1
(get_list() or [None])[0]

这应该工作。

顺便说一下,我没有使用变量list,因为它覆盖了内置函数list()

编辑:我之前有一个稍微简单一点的版本,但是在这里是错误的。


python最惯用的方法是在迭代器上使用next(),因为list是可迭代的。就像@J.F。塞巴斯蒂安在2011年12月13日发表了评论。

如果the_list为空,则返回None。参见next() Python 2.6+

或者如果你确定the_list不是空的:

iter(the_list).next()参见iterator.next() Python 2.2+


OP的解决方案已经差不多了,这里有一些东西可以让它更符合python的风格。

首先,不需要知道列表的长度。Python中的空列表在if检查中计算为False。只是简单地说

1
if list:

此外,给与保留字重叠的变量赋值是一个非常糟糕的主意。"list"是Python中的一个保留字。

我们把它变成

1
2
some_list = get_list()
if some_list:

这里很多解决方案都忽略了一个非常重要的问题,即所有Python函数/方法默认情况下都不返回任何值。试试下面的方法。

1
2
3
4
5
def does_nothing():
    pass

foo = does_nothing()
print foo

除非需要返回None来提前终止函数,否则没有必要显式返回None。非常简洁,只要返回第一个条目,如果它存在的话。

1
2
3
some_list = get_list()
if some_list:
    return list[0]

最后,也许这是隐含的,但只是显式的(因为显式比隐式更好),你不应该让你的函数从另一个函数得到列表;把它作为参数传递进来。最后的结果是

1
2
3
4
5
6
def get_first_item(some_list):
    if some_list:
        return list[0]

my_list = get_list()
first_item = get_first_item(my_list)

就像我说的,OP已经快完成了,只需进行一些触摸就可以让它具有您正在寻找的Python风格。


如果你发现自己试着从列表中找出第一件事(或者没有),你可以切换到生成器来做:

1
next((x for x in blah if cond), None)

优点:如果废话不能索引,那就可以用。缺点:语法不熟悉。不过,在ipython中进行黑客攻击和过滤时,它非常有用。


1
2
for item in get_list():
    return item


Python idiom to return first item or None?

最具python风格的方法就是最受欢迎的答案,当我读到这个问题时,我首先想到的就是这个答案。下面是如何使用它,首先,如果可能的空列表被传递到一个函数:

1
2
def get_first(l):
    return l[0] if l else None

如果列表是从get_list函数返回的:

1
2
l = get_list()
return l[0] if l else None

这里演示了其他实现此目的的方法,并给出了解释

for

当我开始想一些聪明的方法来做这件事的时候,这是我想到的第二件事:

1
2
for item in get_list():
    return item

这假设函数在这里结束,如果get_list返回一个空列表,则隐式返回None。下面显式代码完全等价:

1
2
3
for item in get_list():
    return item
return None

if some_list

下面还提出了(我纠正了不正确的变量名),它也使用了隐式的None。这比上面的方法更好,因为它使用逻辑检查而不是可能不会发生的迭代。这应该更容易立即理解正在发生的事情。但是如果我们写的是可读性和维护性,我们还应该在结尾添加显式的return None:

1
2
3
some_list = get_list()
if some_list:
    return some_list[0]

切片or [None],选择第0个索引

这个问题的答案也是最受欢迎的:

1
return (get_list()[:1] or [None])[0]

切片是不必要的,并在内存中创建一个额外的单条目列表。下面应该更有性能。为了解释,or返回第二个元素,如果第一个元素是布尔上下文中的False,那么如果get_list返回一个空列表,括号中包含的表达式将返回一个带有'None'的列表,然后0索引将访问该列表:

1
return (get_list() or [None])[0]

下一个使用的事实是,如果第一个是布尔上下文中的True,那么返回第二个条目,由于它引用my_list两次,所以它并不比三元表达式好(而且从技术上讲不是一行):

1
2
my_list = get_list()
return (my_list and my_list[0]) or None

next

然后我们就可以巧妙地使用内置函数nextiter

1
return next(iter(get_list()), None)

为了解释,iter返回一个带有.next方法的迭代器。(Python 3中的.__next__)然后内建的next调用那个.next方法,如果迭代器用完了,返回我们给出的默认值None

冗余三元表达式(a if b else c),循环返回

下面的建议,但最好是相反的,因为逻辑通常更好地理解为积极的而不是消极的。由于get_list被调用两次,除非以某种方式对结果进行记忆,否则这将执行得很差:

1
return None if not get_list() else get_list()[0]

更好的逆:

1
return get_list()[0] if get_list() else None

更好的是,使用一个局部变量,这样get_list只被调用一次,并且您首先讨论了推荐的python解决方案:

1
2
l = get_list()
return l[0] if l else None

坦率地说,我认为没有比这更好的成语了:your is clear and short - no need for anything"better"。也许,但这确实是一个品味问题,您可以使用if list:更改if len(list) > 0:—空列表的值总是为False。

另外,Python不是Perl(没有双关语的意思!),您不必获得尽可能最酷的代码。实际上,我在Python中见过的最糟糕的代码也是非常酷的:-),而且完全不可维护。

顺便说一下,我在这里看到的大多数解决方案都没有考虑list[0]计算为False时的情况(例如空字符串或零)——在本例中,它们都返回None,并且没有返回正确的元素。


关于习惯用法,有一个itertools配方称为nth

出现从itertools食谱:

1
2
3
def nth(iterable, n, default=None):
   "Returns the nth item or a default value"
    return next(islice(iterable, n, None), default)

如果您需要一行程序,可以考虑安装一个库来实现这个配方,例如more_itertools:

1
2
3
4
5
6
7
import more_itertools as mit

mit.nth([3, 2, 1], 0)
# 3

mit.nth([], 0)                                             # default is `None`
# None

另一个工具是可用的,它只返回第一项,称为more_itertools.first

1
2
3
4
5
mit.first([3, 2, 1])
# 3

mit.first([], default=None)
# None

这些迭代工具不仅适用于列表,而且适用于任何可迭代的。


出于好奇,我对其中两个解决方案进行了计时。在我的机器上,使用return语句提前结束for循环的解决方案在Python 2.5.1中稍微昂贵一些,我怀疑这与设置iterable有关。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import random
import timeit

def index_first_item(some_list):
    if some_list:
        return some_list[0]


def return_first_item(some_list):
    for item in some_list:
        return item


empty_lists = []
for i in range(10000):
    empty_lists.append([])

assert empty_lists[0] is not empty_lists[1]

full_lists = []
for i in range(10000):
    full_lists.append(list([random.random() for i in range(10)]))

mixed_lists = empty_lists[:50000] + full_lists[:50000]
random.shuffle(mixed_lists)

if __name__ == '__main__':
    ENV = 'import firstitem'
    test_data = ('empty_lists', 'full_lists', 'mixed_lists')
    funcs = ('index_first_item', 'return_first_item')
    for data in test_data:
        print"%s:" % data
        for func in funcs:
            t = timeit.Timer('firstitem.%s(firstitem.%s)' % (
                func, data), ENV)
            times = t.repeat()
            avg_time = sum(times) / len(times)
            print"  %s:" % func
            for time in times:
                print"    %f seconds" % time
            print"    %f seconds avg." % avg_time

这些是我得到的时间:

empty_lists:
  index_first_item:
    0.748353 seconds
    0.741086 seconds
    0.741191 seconds
    0.743543 seconds avg.
  return_first_item:
    0.785511 seconds
    0.822178 seconds
    0.782846 seconds
    0.796845 seconds avg.
full_lists:
  index_first_item:
    0.762618 seconds
    0.788040 seconds
    0.786849 seconds
    0.779169 seconds avg.
  return_first_item:
    0.802735 seconds
    0.878706 seconds
    0.808781 seconds
    0.830074 seconds avg.
mixed_lists:
  index_first_item:
    0.791129 seconds
    0.743526 seconds
    0.744441 seconds
    0.759699 seconds avg.
  return_first_item:
    0.784801 seconds
    0.785146 seconds
    0.840193 seconds
    0.803380 seconds avg.

1
2
3
4
5
6
7
8
def head(iterable):
    try:
        return iter(iterable).next()
    except StopIteration:
        return None

print head(xrange(42, 1000)  # 42
print head([])               # None

顺便说一句:我要把你的程序流程修改成这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
lists = [
    ["first","list"],
    ["second","list"],
    ["third","list"]
]

def do_something(element):
    if not element:
        return
    else:
        # do something
        pass

for li in lists:
    do_something(head(li))

(尽可能避免重复)


这个怎么样:

(my_list and my_list[0]) or None

注意:对于对象列表,这应该可以工作得很好,但是对于下面评论中的数字或字符串列表,它可能返回不正确的答案。


一些人建议这样做:

1
2
list = get_list()
return list and list[0] or None

这在很多情况下都是有效的,但是只有当list[0]不等于0、False或空字符串时才有效。如果list[0]为0、False或空字符串,该方法将错误地返回None。

I've created this bug in my own code one too many times !


那么:next(iter(get_list()), None)呢?在这里可能不是最快的,但它是标准的(从Python 2.6开始),并且很简洁。


使用and-or技巧:

1
2
a = get_list()
return a and a[0] or None

可能不是最快的解决方案,但是没有人提到这个选项:

1
dict(enumerate(get_list())).get(0)

如果get_list()可以返回None,您可以使用:

1
dict(enumerate(get_list() or [])).get(0)

优点:

一线

-你只要打一次电话给get_list()

容易理解


我的用例只是设置一个局部变量的值。

就我个人而言,我发现尝试和除风格更干净的阅读

1
2
3
4
items = [10, 20]
try: first_item = items[0]
except IndexError: first_item = None
print first_item

而不是切一张清单。

1
2
3
items = [10, 20]
first_item = (items[:1] or [None, ])[0]
print first_item

1
2
3
4
try:
    return a[0]
except IndexError:
    return None


1
2
3
4
5
6
7
if mylist != []:

       print(mylist[0])

   else:

       print(None)

您可以使用Extract方法。换句话说,将代码提取到一个方法中,然后调用该方法。

我不会试图压缩它太多,一行代码似乎比冗长的版本更难读。如果你使用提取方法,它是一行;)


难道惯用的python不等同于c风格的三元运算符吗

1
cond and true_expr or false_expr

ie。

1
2
list = get_list()
return list and list[0] or None