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


275

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

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

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

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

Редактировать:

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

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


28
Кстати, на Python 2.6+ вы можете использовать next(iter(your_list), None)вместо first_item(your_list)предположения, your_listчто нет None( get_first_list()и get_second_list()всегда должны возвращать итеративный).
JFS

1
Я думаю, вы имеете в виду next(iter(your_list)), поскольку, если вы предоставляете второй аргумент iter, вы говорите, что первый аргумент может быть вызван.
Роберт Россни

7
Нет, Noneвот второй параметр для next(), нет iter(). next()возвращает второй параметр, если он задан, вместо повышения, StopIterationесли your_listпусто.
JFS

Решение, предложенное @JFSebastian, существует с двойным вопросом: stackoverflow.com/a/18533669/144408
shahjapan

Ответы:


205

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

1
Последний вариант - почти то, что я ищу: он понятен, работает и не требует от меня определения новой функции. Я бы сказал «точно», если перерыв как-то не нужен, потому что риск его пропуска не мал. Но этот подход имеет кольцо правды.
Роберт Россни

О, мне это совсем не нравится. Если какой-либо элемент в списке оценивается как False, это значение отбрасывается и заменяется. Если ""в списке есть пустая строка , она отбрасывается и заменяется пустым списком []. Если у вас есть 0, также заменяется на []. Если у вас Falseтам, тоже заменили. И т.д. Вы можете избежать неприятностей в конкретном случае, но это плохая привычка для развития.
Steveha

4
@steveha: bool(lst) говорит нам, len(lst) > 0не говорит ли нам что-нибудь о том, что элементы lstсодержат, например, bool([False]) == True;поэтому выражение [False] or []возвращается [False], то же самое для [0] or [] него возвращается [0].
JFS

@RobertRossney: Я добавил, yield_first()чтобы избежать breakзаявления.
JFS

1
@ThomasAhle: оба верны. Я обновил ответ, чтобы предоставить решение для не-ни одного случая.
Jfs

218

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

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

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

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

К сожалению. Должен был упомянуть, что это приложение Python 2.4.
Роберт Россни

15
@ Адам: будущее может использоватьnext(iter(your_list), None)
JFS

@JFSebastian на самом деле next(iter(your_list))достаточно
Брэд Джонсон

13
@BradleagheJohnson: неправильно. Он вызывает StopIteration вместо возврата по запросу NoneOP. Попробуйте: next(iter([])).
JFS

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

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

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

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


4
Это умно и работает, но я думаю, что «вернуть foo [0], если foo else None», как предлагает efotinis, немного проще для сопровождения.
Джей

3
Вопрос заключался в том, чтобы найти решение в одну строку, поэтому я исключил это.
рекурсивный

Ни одно из этих выражений не работает, если get_list () возвращает None; вы получаете "TypeError: объект 'NoneType' является неподписанным." или "TypeError: неподдерживаемые типы операндов для +: 'NoneType' и" list "."
Роберт Росни

4
В вопросе говорится, что методы возвращают списки, из которых None не один. Поэтому, учитывая требования, я все еще верю, что они оба работают.
рекурсивный

Должен работать сейчас, если get_list () возвращает None, так как он больше не подписывается и не добавляется.
Роберт

33

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

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

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

iter(the_list).next()смотрите iterator.next () Python 2.2+


1
Это имеет то преимущество, что вам не нужно присваивать свой список переменной, например, return l[0] if l else Noneесли список был libcomp. Когда список на самом деле является genexpr, это даже лучше, так как вам не нужно составлять весь список, как вnext(iter(x for x in range(10) if x % 2 == 0), None)
RubenLaguna

Если вы точно знаете, the_listне пусто, вы бы написали the_list[0].
kaya3

12

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

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

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


11

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

Во-первых, нет необходимости получать длину списка. Пустые списки в 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)

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


1
Почему «список» зарезервированное слово в Python? Или, более конкретно, где говорится, что «список» является словом резервирования в python? Или, более конкретно, как вы убедились, что «список» является зарезервированным словом в Python? Учитывая, что я могу объявить переменную с именем list, это явно не зарезервированное слово.
Лассе В. Карлсен

2
Дело принято. По-настоящему зарезервированные слова в Python можно найти здесь tinyurl.com/424663 Тем не менее, это хорошая идея, чтобы рассматривать встроенные функции и типы как зарезервированные. list () фактически генерирует список. То же самое касается dict, range и т. Д.
gotgenes

Я бы объединил последние две строки, исключив временную переменную (если только вам не нужен my_list). Это улучшает читабельность.
Сванте

Это в значительной степени ответ, к которому я стремился.
Роберт Россни

1
get_first_itemдолжен принять defaultпараметр (например dict.get), который по умолчанию принимает значение None. Таким образом, интерфейс функции явно сообщает, что происходит, если my_listон пуст.
JFS

3
for item in get_list():
    return item

Это лаконично и красиво.
gotgenes

К сожалению, это не работает; он выдает исключение «TypeError: объект NoneType не является итеративным», если get_list () возвращает None.
Роберт Россни

Исправить: "для элемента в get_list () или []:"
itsadok

5
Вопрос ясно предполагает, что get_list возвращает список. len и getitem вызываются на его результат.
А. Коди

2

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

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

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


В Python считается хорошим стилем написания, if lst:а не if len(lst) > 0:. Кроме того, не используйте ключевые слова Python в качестве имен переменных; это заканчивается слезами. Обычно используется Lили в lstкачестве имени переменной для некоторого списка. Я уверен, что в этом случае вы не имели в виду предлагать использовать listв качестве имени переменной, вы просто имели в виду «некоторый список». И +1 для «когда lst [0] оценивается как False».
Steveha

Да, я просто сохранял одно и то же имя OP, для большей ясности. Но вы определенно правы: всегда нужно соблюдать осторожность, чтобы не переопределять ключевые слова Python.
ограбить

2

Идиома 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

Это предполагает, что функция заканчивается здесь, неявно возвращая Noneif 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

2

Что касается идиомы, есть 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 масштабируются для любых итераций, а не только для списков.



1

Из любопытства я выбрал время для двух решений. Решение, которое использует оператор return для преждевременного завершения цикла for, немного дороже на моей машине с Python 2.5.1, я подозреваю, что это связано с настройкой итерируемого.

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 секунды
    0,741086 секунд
    0,741191 секунды
    0,743543 секунды
  return_first_item:
    0,785511 секунд
    0,822178 секунд
    0,782846 секунд
    0,796845 секунд
full_lists:
  index_first_item:
    0,762618 секунд
    0,788040 секунд
    0,786849 секунд
    0,779169 секунд
  return_first_item:
    0,802735 секунд
    0,878706 секунд
    0,808781 секунд
    0,830074 секунды
mixed_lists:
  index_first_item:
    0,791129 секунд
    0,743526 секунд
    0,744441 секунды
    0,759699 секунд
  return_first_item:
    0,784801 секунды
    0,785146 секунд
    0,840193 секунды
    0,803380 секунд в среднем

0
try:
    return a[0]
except IndexError:
    return None

1
Исключения обычно не используются для управления потоком.
Мэтт Грин

Я не думаю, что удлинение этого кода и добавление обработки исключений - это именно та идиома, которую я искал.
Роберт Россни

3
Но это обычная идиома Python! По соображениям производительности вы можете его не использовать; Вы получите лучшую производительность, if len(a) > 0:но это считается хорошим стилем. Обратите внимание, насколько хорошо это выражает проблему: «попытайтесь извлечь заголовок списка, и если это не сработает, вернитесь None». Поиском в Google "EAFP" или посмотрите здесь: python.net/~goodger/projects/pycon/2007/idiomatic/…
steveha

@steveha Это зависит от ваших данных. Если len (a) обычно> 0, а len (a) <1 - редкое состояние, то перехват исключения будет более производительным.
limscoder

@limscoder, я согласен с тобой. Я просто не стал вдаваться в подробности того, когда вы можете не использовать его по соображениям производительности; Я не говорил, что ты никогда не воспользуешься этим.
steveha

0
def head(iterable):
    try:
        return iter(iterable).next()
    except StopIteration:
        return None

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

Кстати, я бы переделал ваш общий поток программ в нечто вроде этого:

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))

(Избегая повторения, когда это возможно)


0

Как насчет этого:

(my_list and my_list[0]) or None

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


Как насчет ([0,1] and 0) or None!
Кенли

Это хороший момент ... Небезопасно использовать с коллекциями чисел в текущей форме :(
VitalyB

То же самое с (['','A'] and '') or None.
Кенли

Если my_list[0]оценивается как False, результат должен быть None.
Кенли

2
my_list[0] if len(my_list) else None
Николас Гамильтон

0

Не уверен, насколько это pythonic, но пока в библиотеке нет первой функции, я включаю ее в источник:

first = lambda l, default=None: next(iter(l or []), default)

Это всего лишь одна строка (соответствует черному цвету) и позволяет избежать зависимостей.



-1

Вероятно, не самое быстрое решение, но никто не упомянул эту опцию:

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

если get_list()можете вернуть Noneвы можете использовать:

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

Преимущества:

-одна линия

-вы просто позвоните get_list()один раз

-Легко понять


-1

Мой вариант использования был только для установки значения локальной переменной.

Лично я нашел попытку и кроме стиля чище читать

items = [10, 20]
try: first_item = items[0]
except IndexError: first_item = None
print first_item

чем нарезать список.

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

-1

Несколько человек предложили сделать что-то вроде этого:

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

Это работает во многих случаях, но будет работать, только если list [0] не равен 0, False или пустой строке. Если list [0] равен 0, False или пустой строке, метод по ошибке вернет None.

Я создал эту ошибку в своем собственном коде слишком много раз!


3
Это должен быть комментарий под ошибочными ответами, а не отдельный ответ.
IJ Кеннеди

-2

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

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



-3

не идиоматический питон, эквивалентный тернарным операторам в стиле C

cond and true_expr or false_expr

то есть.

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

Если [0] - это число 0 или пустая строка (или что-либо еще, что оценивается как ложное), это вернет None вместо того, что на самом деле является [0].
Адам Розенфилд
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.