Идиома Python для возврата первого элемента или None - PullRequest
216 голосов
/ 12 декабря 2008

Я уверен, что есть более простой способ сделать это, что просто не происходит со мной.

Я вызываю кучу методов, которые возвращают список. Список может быть пустым. Если список не пустой, я хочу вернуть первый элемент; в противном случае я хочу вернуть None. Этот код работает:

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

Мне кажется, что для этого должна быть простая однострочная идиома, но я не могу об этом думать. Есть ли?

Edit:

Причина, по которой я ищу здесь однострочное выражение, не в том, что мне нравится невероятно лаконичный код, а в том, что мне приходится писать много такого кода:

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

То, что я хотел бы сделать, безусловно, может быть выполнено с помощью функции (и, вероятно, будет):

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, и я думал, что написание функции было бы глупо, если бы существовало простое выражение, которое могло бы сработать. Но, увидев эти ответы, кажется, что функция является простым решением.

Ответы [ 22 ]

191 голосов
/ 12 декабря 2008

Лучший способ это:

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

Вы также можете сделать это в одну строку, но программисту гораздо сложнее прочитать:

return (get_list()[:1] or [None])[0]
146 голосов
/ 14 декабря 2008

Python 2.6 +

next(iter(your_list), None)

Если your_list может быть None:

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

Python 2,4

def get_first(iterable, default=None):
    if iterable:
        for item in iterable:
            return item
    return default

Пример:

x = get_first(get_first_list())
if x:
    ...
y = get_first(get_second_list())
if y:
    ...

Другой вариант - встроить вышеуказанную функцию:

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, вы можете написать:

for x in yield_first(get_first_list()):
    x # process x
for y in yield_first(get_second_list()):
    y # process y

Где:

def yield_first(iterable):
    for item in iterable or []:
        yield item
        return
63 голосов
/ 12 декабря 2008
(get_list() or [None])[0]

Это должно сработать.

Кстати, я не использовал переменную list, потому что она перезаписывает встроенную функцию list().

Edit: у меня была немного более простая, но неправильная версия здесь.

31 голосов
/ 20 августа 2014

Самый идиоматический способ использования Python - это использовать next () на итераторе, так как список iterable так же, как то, что @ J.F.Sebastian поместил в комментарии 13 декабря 2011 года.

next(iter(the_list), None) Возвращает None, если the_list пусто. см далее () Python 2.6+

или если вы точно знаете, the_list не является пустым:

iter(the_list).next() см. iterator.next () Python 2.2+

10 голосов
/ 13 декабря 2008

Решение ОП уже близко, есть всего несколько вещей, которые делают его более питонским.

Во-первых, нет необходимости получать длину списка. Пустые списки в Python оцениваются как False в проверке if. Просто скажи

if list:

Кроме того, очень плохая идея назначать переменные, которые перекрываются с зарезервированными словами. "список" является зарезервированным словом в Python.

Итак, давайте изменим это на

some_list = get_list()
if some_list:

Действительно важным моментом, который пропускают многие решения, является то, что все функции / методы Python возвращают None по умолчанию . Попробуйте следующее ниже.

def does_nothing():
    pass

foo = does_nothing()
print foo

Если вам не нужно возвращать None для досрочного завершения функции, нет необходимости явно возвращать None. Очень кратко, просто верните первую запись, если она существует.

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

И, наконец, возможно, это подразумевалось, но просто чтобы быть явным (поскольку явное лучше, чем неявное ), вы не должны заставлять вашу функцию получать список из другой функции; просто передайте это как параметр. Итак, окончательный результат будет

def get_first_item(some_list): 
    if some_list:
        return list[0]

my_list = get_list()
first_item = get_first_item(my_list)

Как я уже сказал, ОП уже был на месте, и всего несколько прикосновений придают ему тот вкус Python, который вы ищете.

10 голосов
/ 29 февраля 2016

Если вы пытаетесь вырвать первое (или ничего) из понимания списка, вы можете переключиться на генератор, чтобы сделать это следующим образом:

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

Pro: работает, если бла не индексируется. Con: это незнакомый синтаксис. Это полезно при хакерстве и фильтрации содержимого в ipython.

3 голосов
/ 13 декабря 2008
for item in get_list():
    return item
2 голосов
/ 13 декабря 2008

Честно говоря, я не думаю, что есть лучшая идиома: ваша ясна и лаконична - не нужно ничего «лучшего». Возможно, но это действительно дело вкуса, вы можете изменить if len(list) > 0: на if list: - пустой список всегда будет иметь значение False.

В соответствующей заметке Python - это , а не Perl (без каламбура!), Вам не нужно получать самый крутой код из возможных.
На самом деле, худший код, который я видел в Python, также был очень крутым :-) и совершенно не поддерживаемым.

Кстати, большинство решений, которые я здесь видел, не учитывается, когда list [0] оценивается как False (например, пустая строка или ноль) - в этом случае все они возвращают None и не правильный элемент .

2 голосов
/ 25 августа 2017

Что касается идиом, существует рецепт itertools , называемый nth.

Из рецептов itertools:

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

Если вам нужны однострочные, рассмотрите возможность установки библиотеки, которая реализует этот рецепт для вас, например, more_itertools:

import more_itertools as mit

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

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

Доступен еще один инструмент, который возвращает только первый элемент, который называется more_itertools.first.

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

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

Эти itertools обычно масштабируются для любых итераций, а не только для списков.

2 голосов
/ 18 июля 2015

Python идиома для возврата первого элемента или нет?

Самый питонский подход - это то, что продемонстрировал самый голосующий голос, и это было первое, что пришло мне в голову, когда я прочитал вопрос. Вот как его использовать, во-первых, если в функцию передан, возможно, пустой список:

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

И если список возвращается из функции get_list:

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

Другие способы продемонстрировать это здесь, с объяснениями

for

Когда я начал пытаться придумать умные способы сделать это, это второе, о чем я подумал:

for item in get_list():
    return item

Это предполагает, что функция заканчивается здесь, неявно возвращая None, если get_list возвращает пустой список. Приведенный ниже явный код в точности эквивалентен:

for item in get_list():
    return item
return None

if some_list

Было также предложено следующее (я исправил неправильное имя переменной), в котором также используется неявное None. Это было бы предпочтительнее вышеописанного, так как в нем используется логическая проверка вместо итерации, которая может не произойти. Это должно быть легче сразу понять, что происходит. Но если мы пишем для удобочитаемости и удобства обслуживания, нам также следует добавить в конце явное return None:

some_list = get_list()
if some_list:
    return some_list[0]

срез or [None] и выбор нулевого индекса

Этот вопрос также входит в число самых популярных:

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

Срез не нужен, и создает дополнительный список из одного элемента в памяти. Следующее должно быть более производительным. Для объяснения, or возвращает второй элемент, если первый - False в логическом контексте, поэтому, если get_list возвращает пустой список, выражение, содержащееся в скобках, вернет список с 'None', который затем быть доступным по индексу 0:

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

Следующий использует тот факт, что и возвращает второй элемент, если первый - True в логическом контексте, и поскольку он дважды ссылается на my_list, он не лучше троичного выражения (и технически не является однострочным) ):

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

next

Тогда у нас есть следующее умное использование встроенных next и iter

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

Для объяснения iter возвращает итератор с методом .next. (.__next__ в Python 3.) Затем встроенный next вызывает этот метод .next и, если итератор исчерпан, возвращает заданное нами значение по умолчанию: None.

Избыточное троичное выражение (a if b else c) и кружение назад

Было предложено следующее, но обратное было бы предпочтительнее, так как логика обычно лучше понимается в положительном, а не отрицательном. Поскольку get_list вызывается дважды, если результат не запомнен каким-либо образом, это будет плохо работать:

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

Лучше обратное:

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

Еще лучше, используйте локальную переменную, чтобы get_list вызывался только один раз, и у вас сначала было рекомендовано решение Pythonic:

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