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

StackOverflow https://stackoverflow.com/questions/363944

  •  21-08-2019
  •  | 
  •  

Вопрос

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

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

Это было полезно?

Решение

Питон 2.6+

next(iter(your_list), None)

Если your_list возможно None:

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

Питон 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

Другие советы

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

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

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

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

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

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

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

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

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

или если ты знаешь наверняка the_list не пусто:

iter(the_list).next() видеть итератор.next() Python 2.2+

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

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

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, который вы ищете.

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

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

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

for item in get_list():
    return item

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

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

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

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

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

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 возвращает пустой список, выражение, содержащееся в скобках, вернет список со значением «Нет», к которому затем будет обращаться 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

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

Из любопытства я проверил тайминги двух решений.Решение, использующее оператор 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 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.
try:
    return a[0]
except IndexError:
    return None
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))

(По возможности избегайте повторений)

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

(my_list and my_list[0]) or None

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

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

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

Использование трюка «и-или»:

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

И что насчет: next(iter(get_list()), None)?Возможно, он не самый быстрый, но стандартный (начиная с Python 2.6) и краткий.

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

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

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

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

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

-одна линия

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

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

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

Лично я нашел очиститель стилей «попробуй и за исключением», чтобы прочитать

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
if mylist != []:

       print(mylist[0])

   else:

       print(None)

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

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

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

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

не является ли идиоматический Python эквивалентом тернарных операторов в стиле C?

cond and true_expr or false_expr

то есть.

list = get_list()
return list and list[0] or None
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top