Idiom Pythona, aby zwrócić pierwszy element lub Brak


273

Jestem pewien, że istnieje prostszy sposób na zrobienie tego, co po prostu mi się nie zdarza.

Nazywam wiele metod, które zwracają listę. Lista może być pusta. Jeśli lista nie jest pusta, chcę zwrócić pierwszą pozycję; w przeciwnym razie chcę zwrócić Brak. Ten kod działa:

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

Wydaje mi się, że powinien istnieć prosty, jednowierszowy idiom do zrobienia tego, ale przez całe życie nie mogę o tym myśleć. Jest tu?

Edytować:

Powodem, dla którego szukam tutaj wyrażenia jednowierszowego, nie jest to, że lubię niewiarygodnie zwięzły kod, ale dlatego, że muszę napisać dużo takiego kodu:

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

To, co chciałbym robić, z pewnością można osiągnąć za pomocą funkcji (i prawdopodobnie będzie):

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

Zadałem pytanie, ponieważ często jestem zaskoczony tym, co potrafią proste wyrażenia w Pythonie, i pomyślałem, że napisanie funkcji było głupią rzeczą, gdyby istniało proste wyrażenie. Ale widząc te odpowiedzi, wydaje się, że funkcja jest prostym rozwiązaniem.


28
btw, w Pythonie 2.6+ można użyć next(iter(your_list), None)zamiast first_item(your_list)zakładać, że your_listnie None( get_first_list()i get_second_list()zawsze musi zwrócić iterowalną).
jfs

1
Myślę, że masz na myśli next(iter(your_list)), ponieważ jeśli podasz drugi argument iter, mówisz mu, że pierwszy argument jest możliwy do wywołania.
Robert Rossney,

7
Nie, Nonetutaj jest drugi parametr dla next(), nie iter(). next()zwraca drugi parametr, jeśli podano, zamiast podnoszenia, StopIterationjeśli your_listjest pusty.
jfs

Rozwiązanie sugerowane przez @JFSebastian istnieje ze zduplikowanym pytaniem: stackoverflow.com/a/18533669/144408
shahjapan

Odpowiedzi:


205

Python 2.6+

next(iter(your_list), None)

Jeśli your_listmoże być 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

Przykład:

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

Inną opcją jest wstawienie powyższej funkcji:

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

Aby tego uniknąć, breakmożesz napisać:

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

Gdzie:

def yield_first(iterable):
    for item in iterable or []:
        yield item
        return

1
Ta ostatnia opcja jest prawie dokładnie tym, czego szukam: jest jasna, działa i nie wymaga ode mnie definiowania nowej funkcji. Powiedziałbym „dokładnie”, gdyby przerwa w jakiś sposób nie była potrzebna, ponieważ ryzyko jej pominięcia nie jest nieznaczne. Ale to podejście ma pierścień prawdy.
Robert Rossney,

Och, wcale mi się to nie podoba. Jeśli jakikolwiek element na liście ma wartość False, wartość ta jest odrzucana i zastępowana. Jeśli ""na liście znajduje się pusty ciąg , jest on odrzucany i zastępowany pustą listą []. Jeśli masz 0, również zastąpione przez []. Jeśli masz Falsetam, również wymienione. Itd. Możesz uciec od tego w konkretnym przypadku, ale jest to zły nawyk do rozwijania się.
steveha

4
@steveha: bool(lst) mówi nam, czy len(lst) > 0nie mówi nam nic o tym, co lstzawiera, np. bool([False]) == True;dlatego wyrażenie [False] or []zwraca [False], to samo dotyczy [0] or [] zwrotu [0].
jfs

@RobertRossney: Dodałem, yield_first()aby uniknąć breakoświadczenia.
jfs

1
@ThomasAhle: oba są prawdziwe. Zaktualizowałem odpowiedź, aby zapewnić rozwiązanie dla przypadku nie-Brak.
jfs

218

Najlepszy sposób to:

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

Możesz także zrobić to w jednym wierszu, ale programiście jest o wiele trudniej odczytać:

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

Ups Powinienem wspomnieć, że jest to aplikacja Python 2.4.
Robert Rossney,

15
@Adam: może skorzystać z przyszłościnext(iter(your_list), None)
jfs

@JFSebastian faktycznie next(iter(your_list))wystarczy
Brad Johnson

13
@BradleagheJohnson: źle. Podnosi StopIteration zamiast powrotu, Nonejak prosi OP. Spróbuj: next(iter([])).
jfs

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

To powinno działać.

BTW Nie użyłem zmiennej list, ponieważ to zastępuje wbudowaną list()funkcję.

Edycja: Miałem wcześniej nieco prostszą, ale złą wersję.


4
Jest to sprytne i działa, ale myślę, że „return foo [0] jeśli foo else None”, jak sugeruje efotinis, jest nieco łatwiejsze do wykonania w celu konserwacji.
Jay

3
Pytanie dotyczyło rozwiązania jednoliniowego, więc wykluczyłem to.
rekurencyjny

Żadne z tych wyrażeń nie działa, jeśli get_list () zwraca None; pojawia się komunikat „TypeError: Obiekt„ NoneType ”nie podlega skryptowi”. lub „TypeError: nieobsługiwane typy operandów dla +:„ NoneType ”i„ list ”.”
Robert Rossney,

4
W pytaniu jest powiedziane, że metody zwracają listy, z których Żaden nie jest jednym. Biorąc pod uwagę wymagania, nadal uważam, że oba działają.
rekurencyjny

Powinien działać teraz, jeśli get_list () zwraca None, ponieważ nie jest już indeksowany ani dodawany.
Robert

32

Najbardziej idiomatycznym sposobem Pythona jest użycie funkcji next () w iteratorze, ponieważ lista jest iterowalna . dokładnie tak, jak @JFSebastian w komentarzu z 13 grudnia 2011 r.

next(iter(the_list), None)Zwraca Brak, jeśli the_listjest pusty. zobacz next () Python 2.6+

lub jeśli wiesz na pewno the_listnie jest pusty:

iter(the_list).next()zobacz iterator.next () Python 2.2+


1
Ma to tę zaletę, że nie trzeba przypisywać listy do zmiennej, takiej jak return l[0] if l else Nonegdyby lista była libcomp. Kiedy lista faktycznie jest genexpr, jest to nawet lepsze, ponieważ nie musisz budować całej listy jak wnext(iter(x for x in range(10) if x % 2 == 0), None)
RubenLaguna

Jeśli wiesz na pewno, że the_listnie jest pusty, napiszesz the_list[0].
kaya3

12

Jeśli próbujesz wyciągnąć pierwszą rzecz (lub brak) ze zrozumienia listy, możesz przełączyć się na generator, aby to zrobić w następujący sposób:

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

Pro: działa, jeśli bla nie jest indeksowalny Con: to nieznana składnia. Jest to przydatne podczas hakowania i filtrowania rzeczy w ipython.


11

Rozwiązanie OP jest już blisko, jest tylko kilka rzeczy, aby uczynić go bardziej Pythonic.

Po pierwsze, nie ma potrzeby uzyskiwania długości listy. Puste listy w Pythonie oceniają na False w czeku if. Po prostu powiedz

if list:

Dodatkowo przypisywanie zmiennych zmiennych nakładających się na słowa zastrzeżone jest bardzo złym pomysłem. „lista” jest słowem zastrzeżonym w języku Python.

Zmieńmy to na

some_list = get_list()
if some_list:

Naprawdę ważną kwestią, której brakuje wielu rozwiązaniom, jest to, że wszystkie funkcje / metody Pythona domyślnie zwracają Brak . Spróbuj wykonać poniższe czynności.

def does_nothing():
    pass

foo = does_nothing()
print foo

O ile nie musisz zwracać None, aby wcześniej zakończyć działanie funkcji, nie jest konieczne jawne zwracanie None. Zupełnie zwięźle, po prostu zwróć pierwszy wpis, jeśli istnieje.

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

I na koniec, być może zostało to dorozumiane, ale żeby być jawnym (ponieważ jawne jest lepsze niż niejawne ), nie powinieneś mieć swojej funkcji, aby pobierała listę z innej funkcji; po prostu przekaż to jako parametr. Tak więc końcowy wynik byłby

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

my_list = get_list()
first_item = get_first_item(my_list)

Jak powiedziałem, OP już prawie się pojawił, a zaledwie kilka dotknięć nadaje mu smak Pythona, którego szukasz.


1
Dlaczego „lista” jest słowem zastrzeżonym w pythonie? Lub, bardziej do rzeczy, gdzie mówi się, że „lista” jest słowem zwrotnym w pythonie? Lub, bardziej konkretnie, w jaki sposób zweryfikowałeś, że „lista” jest słowem zastrzeżonym w pythonie? Biorąc pod uwagę, że mogę zadeklarować zmienną o nazwie „lista”, to wyraźnie nie jest to słowo zastrzeżone.
Lasse V. Karlsen,

2
Punkt wzięty. Naprawdę zastrzeżone słowa w Pythonie można znaleźć tutaj tinyurl.com/424663 Jednak dobrym pomysłem jest rozważenie wbudowanych funkcji i typów jako zastrzeżone. list () faktycznie generuje listę. To samo dotyczy dykt, zasięgu itp.
gotgenes,

Chciałbym połączyć dwa ostatnie wiersze, eliminując zmienną tymczasową (chyba, że ​​więcej potrzebujesz mojej_listy). Poprawia to czytelność.
Svante,

To właściwie odpowiedź, do której dążyłem.
Robert Rossney,

1
get_first_itempowinien zaakceptować defaultparametr (jak dict.get), który domyślnie przyjmuje wartość Brak. W ten sposób interfejs funkcji wyraźnie komunikuje, co się stanie, jeśli my_listbędzie pusty.
jfs

3
for item in get_list():
    return item

To jest zwięzłe i piękne.
gotgenes,

Niestety, to nie działa; zgłasza wyjątek „TypeError:„ NoneType ”obiekt nie jest iterowalny”, jeśli get_list () zwraca None.
Robert Rossney,

Aby naprawić: „dla elementu w get_list () lub []:”
itsadok

5
Pytanie wyraźnie zakłada, że ​​get_list zwraca listę. len i getitem są wywoływane na jego wynik.
A. Coady

2

Szczerze mówiąc, nie sądzę, aby istniał lepszy idiom: twój jest jasny i zwięzły - nie potrzebujesz niczego „lepszego”. Być może, ale to naprawdę kwestia gustu, którą możesz zmienić za if len(list) > 0:pomocą if list:- pusta lista zawsze będzie oznaczać False.

W pokrewnej notatce Python nie jest Perlem (nie ma sensu!), Nie musisz uzyskiwać najfajniejszego możliwego kodu.
W rzeczywistości najgorszy kod, jaki widziałem w Pythonie, był również bardzo fajny :-) i całkowicie niemożliwy do utrzymania.

Nawiasem mówiąc, większość rozwiązań, które tu widziałem, nie biorą pod uwagę, gdy lista [0] ma wartość False (np. Pusty ciąg znaków lub zero) - w tym przypadku wszystkie zwracają None i niewłaściwy element.


W Pythonie uważa się za dobry styl pisania if lst:zamiast if len(lst) > 0:. Nie używaj też słów kluczowych Python jako nazw zmiennych; kończy się we łzach. Często stosuje się Llub lstjako nazwę zmiennej dla niektórych list. Jestem pewien, że w tym przypadku nie miałeś zamiaru sugerować używania listnazwy zmiennej, po prostu miałeś na myśli „jakąś listę”. I +1 dla „kiedy lst [0] ocenia na False”.
steveha

Tak, po prostu utrzymywałem tę samą nazwę OP, dla większej przejrzystości. Ale masz całkowitą rację: zawsze należy zachować ostrożność, aby nie zastąpić słów kluczowych w języku Python.
rob

2

Python idiom, aby zwrócić pierwszy element lub brak?

Najbardziej pytoniczne podejście jest tym, co wykazała najbardziej pozytywna odpowiedź, i po raz pierwszy przyszło mi do głowy, gdy czytam pytanie. Oto jak go użyć, po pierwsze, jeśli ewentualnie pusta lista zostanie przekazana do funkcji:

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

A jeśli lista zostanie zwrócona z get_listfunkcji:

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

Wykazano, że można to zrobić tutaj w inny sposób, wraz z objaśnieniami

for

Kiedy zacząłem wymyślać sprytne sposoby na zrobienie tego, to druga rzecz, o której pomyślałem:

for item in get_list():
    return item

Zakłada się, że funkcja kończy się tutaj, domyślnie zwraca, Nonejeśli get_listzwraca pustą listę. Poniższy jawny kod jest dokładnie równoważny:

for item in get_list():
    return item
return None

if some_list

Zaproponowano również następujące (poprawiłem niepoprawną nazwę zmiennej), która również korzysta z niejawnej None. Byłoby to lepsze niż powyższe, ponieważ wykorzystuje sprawdzanie logiczne zamiast iteracji, która może się nie zdarzyć. Powinno to być łatwiejsze do natychmiastowego zrozumienia, co się dzieje. Ale jeśli piszemy dla czytelności i łatwości konserwacji, powinniśmy również dodać return Nonena końcu wyraźne :

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

pokroić or [None]i wybrać indeks zerowy

Ten jest również w najczęściej głosowanej odpowiedzi:

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

Wycinek jest niepotrzebny i tworzy w pamięci dodatkową listę z jednym elementem. Następujące powinny być bardziej wydajne. Aby to wyjaśnić, orzwraca drugi element, jeśli pierwszy znajduje się Falsew kontekście logicznym, więc jeśli get_listzwróci pustą listę, wyrażenie zawarte w nawiasach zwróci listę z „Brak”, do której dostęp uzyska następnie 0indeks:

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

Następny wykorzystuje fakt, że zwraca drugi element, jeśli pierwszy jest Truew kontekście logicznym, a ponieważ dwukrotnie odwołuje się do mojej_listy, nie jest lepszy niż wyrażenie trójkowe (i technicznie nie jest to jednowierszowy):

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

next

Następnie mamy następujące sprytne użycie wbudowanego nextiiter

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

Aby wyjaśnić, iterzwraca iterator z .nextmetodą. ( .__next__W Pythonie 3.), a następnie wbudowane nextpołączenia, które .nextmetody, a jeśli iterator jest wyczerpany, przywraca domyślne dajemy, None.

redundantne wyrażenie trójskładnikowe ( a if b else c) i krążące w tył

Zaproponowano poniżej, ale odwrotność byłaby lepsza, ponieważ logika jest zwykle lepiej rozumiana w pozytywnych zamiast negatywnych. Ponieważ get_listjest wywoływany dwukrotnie, chyba że wynik zostanie w jakiś sposób zapamiętany, to działałoby to źle:

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

Im lepsza odwrotność:

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

Co więcej, użyj zmiennej lokalnej, która get_listjest wywoływana tylko raz, i najpierw omówiono zalecane rozwiązanie w języku Python:

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

2

Jeśli chodzi o idiomy, istnieje przepis itertools o nazwie nth.

Z przepisów itertools:

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

Jeśli chcesz mieć jeden wiersz, rozważ zainstalowanie biblioteki, która implementuje ten przepis dla Ciebie, np . more_itertools:

import more_itertools as mit

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

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

Dostępne jest inne narzędzie, które zwraca tylko pierwszy element o nazwie more_itertools.first.

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

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

Te narzędzia itertools są skalowane ogólnie dla dowolnej iteracji, nie tylko dla list.



1

Z ciekawości sprawdziłem czasy dwóch rozwiązań. Rozwiązanie wykorzystujące instrukcję return do przedwczesnego zakończenia pętli for jest nieco droższe na moim komputerze z Pythonem 2.5.1. Podejrzewam, że ma to związek z konfiguracją iterowalności.

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

Oto czasy, które otrzymałem:

puste_listy:
  index_first_item:
    0,748353 sekundy
    0,741086 sekund
    0,741191 sekund
    0,743543 sekund śr.
  return_first_item:
    0,785511 sekund
    0,822178 sekund
    0,782846 sekund
    0,796845 sekund śr.
pełne listy:
  index_first_item:
    0,762618 sekund
    0,788040 sekund
    0,786849 sekund
    0,779169 sekund śr.
  return_first_item:
    0,802735 sekund
    0,878706 sekund
    0,808781 sekund
    Średnio 0,830074 sekundy
mixed_lists:
  index_first_item:
    0,791129 sekund
    0,743526 sekund
    0,744441 sekundy
    0,759699 sekund śr.
  return_first_item:
    0,784801 sekund
    0,785146 sekund
    0,840193 sekundy
    0,803380 sekund śr.

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

1
Wyjątki zasadniczo nie są używane do kontroli przepływu.
Matt Green,

Nie sądzę, aby wydłużanie tego kodu i dodawanie obsługi wyjątków było dokładnie tym idiomem, którego szukałem.
Robert Rossney,

3
Ale to jest powszechny idiom Pythona! Ze względu na wydajność możesz go nie używać; uzyskasz lepszą wydajność, if len(a) > 0:ale jest to uważane za dobry styl. Zwróć uwagę, jak dobrze to wyraża problem: „spróbuj wyodrębnić nagłówek listy, a jeśli to nie zadziała, wróć None”. Wyszukaj w Google „EAFP” lub spójrz tutaj: python.net/~goodger/projects/pycon/2007/idiomatic/…
steveha

@steveha To zależy od twoich danych. Jeśli len (a) jest zwykle> 0, a len (a) <1 jest rzadkim stanem, wyłapanie wyjątku będzie w rzeczywistości bardziej wydajne.
limscoder

@limscoder, zgadzam się z tobą. Po prostu nie wdałem się w szczegóły, kiedy możesz nie używać go ze względu na wydajność; Nie powiedziałem, że nigdy tego nie użyjesz.
steveha

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

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

BTW: Chciałbym przerobić ogólny przebieg programu na coś takiego:

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

(Unikanie powtarzania, gdy tylko jest to możliwe)


0

Co powiesz na to:

(my_list and my_list[0]) or None

Uwaga: Powinno to działać poprawnie w przypadku list obiektów, ale może zwrócić niepoprawną odpowiedź w przypadku listy liczb lub ciągów w komentarzach poniżej.


A co z ([0,1] and 0) or None!
Kenly,

To dobry punkt ... Niebezpieczne w użyciu z kolekcjami liczb w bieżącej formie :(
VitalyB

To samo z (['','A'] and '') or None.
Kenly,

Jeśli my_list[0]zostanie ocenione jako False, wynik powinien być None.
Kenly,

2
my_list[0] if len(my_list) else None
Nicholas Hamilton

0

Nie jestem pewien, jak to jest pythonowe, ale dopóki nie pojawi się pierwsza funkcja w bibliotece, dołączam to do źródła:

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

To tylko jedna linia (odpowiada czerni) i pozwala uniknąć zależności.



-1

Prawdopodobnie nie jest to najszybsze rozwiązanie, ale nikt nie wspomniał o tej opcji:

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

jeśli get_list()możesz wrócić None, możesz użyć:

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

Zalety:

-jedna linia

- zadzwoń tylko get_list()raz

-łatwy do zrozumienia


-1

Mój przypadek użycia polegał jedynie na ustawieniu wartości zmiennej lokalnej.

Osobiście znalazłem próbę i oprócz czyszczenia stylu do przeczytania

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

niż krojenie listy.

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

-1

Kilka osób zasugerowało zrobienie czegoś takiego:

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

Działa to w wielu przypadkach, ale zadziała tylko wtedy, gdy lista [0] nie jest równa 0, False lub pusty ciąg znaków. Jeśli lista [0] ma wartość 0, Fałsz lub pusty ciąg znaków, metoda niepoprawnie zwróci Brak.

Ten błąd utworzyłem we własnym kodzie zbyt wiele razy!


3
Powinien to być komentarz pod błędnymi odpowiedziami, a nie samodzielna odpowiedź.
IJ Kennedy

-2

Możesz użyć metody wyodrębnienia . Innymi słowy, wypakuj ten kod do metody, którą następnie wywołasz.

Nie próbowałbym go bardziej kompresować, te linijki wydają się trudniejsze do odczytania niż wersja pełna. A jeśli użyjesz metody ekstrakcji, jest to jedna linijka;)



-3

nie jest idiomatycznym pytonem równoważnym do operatorów trójskładnikowych w stylu C.

cond and true_expr or false_expr

to znaczy.

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

Jeśli [0] jest liczbą 0 lub pustym ciągiem (lub czymkolwiek innym, co daje wynik fałszywy), to zwróci Brak zamiast tego, czym faktycznie jest [0].
Adam Rosenfield,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.