Ідіома Python для повернення першого елемента або None


273

Я впевнений, що є простіший спосіб зробити це, який мені просто не приходить.

Я закликаю купу методів, які повертають список. Список може бути порожнім. Якщо список не порожній, я хочу повернути перший елемент; інакше я хочу повернути жодного. Цей код працює:

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
btw, на 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
Останній варіант - це саме те, що я шукаю: зрозуміло, він працює, і це не вимагає від мене визначення нової функції. Я б сказав "точно", якби перерва якось не була потрібна, оскільки ризик її пропустити не є незначним. Але такий підхід має кільце істини.
Роберт Россні

О, мені це зовсім не подобається. Якщо будь-який елемент у списку оцінює Неправильно, це значення відкидається та замінюється. Якщо у вас є порожній рядок ""у списку, він викидається та замінюється порожнім списком []. Якщо у вас є 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: і те й інше. Я оновив відповідь, щоб надати рішення для випадку, що не стосується None.
jfs

218

Найкращий спосіб такий:

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

Ви також можете це зробити в одному рядку, але програмісту набагато складніше читати:

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

На жаль Повинен був згадати це додаток Python 2.4.
Роберт Россні

15
@Adam: майбутнє може використовуватисьnext(iter(your_list), None)
jfs

@JFSebastian насправді next(iter(your_list))достатньо
Бред Джонсон

13
@BradleagheJohnson: неправильно. Він піднімає StopIteration, а не повертається, Noneяк просить OP. Спробуйте: next(iter([])).
jfs

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

Це має спрацювати.

BTW Я не використовував змінну list, тому що вона переписує вбудовану list()функцію.

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


4
Це розумно і працює, але я думаю, що "повернути foo [0] якщо foo else None", як запропонував efotinis, трохи легше слідкувати за технічним обслуговуванням.
Джей

3
Питання полягало в тому, щоб запропонувати однолінійне рішення, тому я виключив це.
рекурсивна

Жоден із цих виразів не працює, якщо get_list () не повертає None; ви отримуєте "TypeError: 'NoneType' об'єкт не підписується." або "TypeError: непідтримувані типи операндів для +:" NoneType "і" list "."
Роберт Россні

4
У запитанні йдеться про те, що методи повертають списки, з яких жоден не є одним. Отже, з огляду на вимоги, я все ще вважаю, що вони працюють.
рекурсивна

Має працювати зараз, якщо get_list () повертає None, оскільки він більше не підписується або додається.
Роберт

32

Найбільш ідіоматичним способом 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: працює, якщо blah не індексується Con: це незнайомий синтаксис. Це корисно, хоч взлом і фільтрування речей в ipython.


11

Рішення ОП майже немає, є лише декілька речей, щоб зробити це пітонічнішим.

Для одного немає необхідності отримувати довжину списку. Порожні списки в Python оцінюються як 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, який ви шукаєте.


1
Чому "список" є застереженим словом у python? Або, до речі, де сказано, що "список" є резервним словом у python? Або, до речі, як ви переконалися, що "список" є застереженим словом у python? Зважаючи на те, що я можу оголосити змінну з назвою "список", явно це не зарезервоване слово.
Лассе В. Карлсен

2
Точка взята. Справді зарезервовані слова в Python можна знайти тут tinyurl.com/424663 Однак, непогана ідея вважати вбудовані функції та типи зарезервованими. list () фактично генерує список. Те саме стосується dict, range тощо
gotgenes

Я б поєднав останні два рядки, усунувши тимчасову змінну (якщо вам далі не потрібен мій список). Це покращує читабельність.
Svante

Це в значній мірі відповідь, до якої я тяжів.
Роберт Россні

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] оцінюється як хибне".
steveha

Так, я просто підтримував таку ж назву ОП, для кращої ясності. Але ви, безумовно, праві: завжди слід подбати про те, щоб не перекривати ключові слова Python.
пограбувати

2

Ідіома 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повертає порожній список, вираз, що міститься в дужках, поверне список з "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

З цікавості я провів таймінги на два рішення. Рішення, яке використовує оператор повернення для передчасного закінчення циклу, є трохи дорожчим на моїй машині з 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 с.
повні списки:
  index_first_item:
    0,762618 секунд
    0,788040 секунд
    0,786849 секунд
    Серп. 0,779169 с.
  return_first_item:
    0,802735 секунд
    0,878706 секунд
    0.808781 секунди
    Серп. 0,830074 с.
змішані списки:
  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

BTW: Я б переробив ваш загальний потік програми на щось подібне:

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

Не впевнений, наскільки це пітонічно, але поки в бібліотеці не з’явиться перша функція, я включу це у джерело:

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

Це працює у багатьох випадках, але воно працюватиме лише у тому випадку, якщо список [0] не дорівнює 0, хибному чи порожньому рядку. Якщо список [0] дорівнює 0, помилковий або порожній рядок, метод буде неправильно повернути None.

Я надто багато разів створив цю помилку у власному коді!


3
Це має бути коментар під хибною відповіддю, а не окремою відповіддю.
IJ Kennedy

-2

Ви можете використовувати метод вилучення . Іншими словами, витягніть цей код у метод, який ви б потім зателефонували.

Я б не намагався стиснути його набагато більше, один лайнер здається важче прочитати, ніж багатослівний варіант. І якщо ви використовуєте метод Extract, це один вкладиш;)



-3

не є ідіоматичним пітоном, еквівалентним потрійним операторам у стилі С

cond and true_expr or false_expr

тобто.

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

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