Idioma di Python per restituire il primo oggetto o Nessuno


274

Sono sicuro che c'è un modo più semplice di farlo che non mi sta succedendo.

Sto chiamando un mucchio di metodi che restituiscono un elenco. L'elenco potrebbe essere vuoto. Se l'elenco non è vuoto, desidero restituire il primo elemento; altrimenti, voglio restituire None. Questo codice funziona:

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

Mi sembra che ci dovrebbe essere un semplice linguaggio a una linea per farlo, ma per la mia vita non riesco a pensarci. È lì?

Modificare:

Il motivo per cui sto cercando un'espressione di una riga qui non è che mi piace il codice incredibilmente conciso, ma perché devo scrivere un sacco di codice come questo:

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

Quello che mi piacerebbe fare può certamente essere realizzato con una funzione (e probabilmente lo sarà):

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

Ho pubblicato la domanda perché sono spesso sorpreso da ciò che possono fare semplici espressioni in Python e ho pensato che scrivere una funzione fosse una cosa sciocca da fare se esistesse una semplice espressione che potesse fare il trucco. Ma vedendo queste risposte, sembra che una funzione sia la soluzione semplice.


28
a proposito, su Python 2.6+ potresti usare next(iter(your_list), None)invece di first_item(your_list)supporre che your_listnon lo sia None( get_first_list()e get_second_list()deve sempre restituire un iterabile).
jfs,

1
Penso che intendi next(iter(your_list)), dal momento che se fornisci un secondo argomento iter, lo stai dicendo che il primo argomento è richiamabile.
Robert Rossney,

7
No, Noneecco il secondo parametro per next(), no iter(). next()restituisce il secondo parametro se indicato invece di aumentare StopIterationse your_listè vuoto.
jfs,

Soluzione suggerita da @JFSebastian esiste con la domanda duplicato: stackoverflow.com/a/18533669/144408
shahjapan

Risposte:


205

Python 2.6+

next(iter(your_list), None)

Se your_listpuò essere 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

Esempio:

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

Un'altra opzione è incorporare la funzione sopra:

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

Per evitare che breaktu possa scrivere:

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

Dove:

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

1
L'ultima opzione è quasi esattamente ciò che sto cercando: è chiara, funziona e non mi richiede di definire una nuova funzione. Direi "esattamente" se la pausa non fosse in qualche modo necessaria, perché il rischio di ometterla non è insignificante. Ma questo approccio ha l'anello della verità.
Robert Rossney,

Oh, non mi piace per niente. Se uno degli elementi nell'elenco è False, quel valore viene scartato e sostituito. Se hai una stringa vuota ""nell'elenco, questa viene scartata e sostituita da un elenco vuoto []. Se hai uno 0, anche sostituito da []. Se ci sei False, anche sostituito. Ecc. Potresti cavartela in un caso specifico, ma questa è una cattiva abitudine da sviluppare.
Steveha,

4
@steveha: bool(lst) ci dice se len(lst) > 0non ci dice nulla su ciò che lstcontiene gli elementi , ad esempio, bool([False]) == True;quindi l'espressione [False] or []ritorna [False], lo stesso vale per [0] or [] essa ritorna [0].
jfs,

@RobertRossney: ho aggiunto yield_first()per evitare la breakdichiarazione.
jfs,

1
@ThomasAhle: entrambi sono veri. Ho aggiornato la risposta per fornire la soluzione per casi diversi da Nessuno.
jfs,

218

Il modo migliore è questo:

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

Potresti anche farlo in una riga, ma è molto più difficile per il programmatore leggere:

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

Ops. Avrei dovuto menzionare che si tratta di un'app Python 2.4.
Robert Rossney,

15
@Adam: il futuro potrebbe usarenext(iter(your_list), None)
jfs

@JFSebastian in realtà next(iter(your_list))è sufficiente
Brad Johnson

13
@BradleagheJohnson: sbagliato. Solleva StopIteration invece di tornare Nonecome chiede OP. Prova: next(iter([])).
jfs

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

Dovrebbe funzionare.

A proposito, non ho usato la variabile list, perché questo sovrascrive la list()funzione integrata.

Modifica: avevo una versione leggermente più semplice, ma sbagliata qui prima.


4
Questo è intelligente e funziona, ma penso che "return foo [0] if foo else None" come suggerito da efotinis è un po 'più facile da seguire per la manutenzione.
Jay,

3
La domanda era una soluzione a una riga, quindi l'ho esclusa.
ricorsivo

Nessuna di queste espressioni funziona se get_list () restituisce None; si ottiene "TypeError: l'oggetto 'NoneType' non è sottoscrivibile." o "TypeError: tipi di operando non supportati per +: 'NoneType' e 'list'."
Robert Rossney,

4
Nella domanda, afferma che i metodi restituiscono elenchi, di cui Nessuno non è uno. Quindi, dati i requisiti, credo ancora che entrambi funzionino.
ricorsivo

Dovrebbe funzionare ora se get_list () restituisce None poiché non viene più sottoscritto o aggiunto.
Robert,

32

Il modo più idiomatico di Python è usare next () su un iteratore poiché list è iterabile . proprio come quello che @JFSebastian ha inserito nel commento il 13 dicembre 2011.

next(iter(the_list), None)Questo restituisce None se the_listè vuoto. vedi il prossimo () Python 2.6+

o se sai per certo che the_listnon è vuoto:

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


1
Questo ha il vantaggio di non dover assegnare la tua lista a una variabile come return l[0] if l else Nonese la lista fosse una libcomp. Quando l'elenco è in realtà un genexpr, questo è ancora meglio poiché non è necessario costruire l'intero elenco come innext(iter(x for x in range(10) if x % 2 == 0), None)
RubenLaguna,

Se sai per certo che the_listnon è vuoto, scriveresti the_list[0].
kaya3,

12

Se ti ritrovi a provare a cogliere la prima cosa (o nessuna) da una comprensione della lista, puoi passare a un generatore per farlo come:

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

Pro: funziona se blah non è indicizzabile Con: è sintassi sconosciuta. È utile durante l'hacking e il filtraggio di cose in ipython però.


11

La soluzione dell'OP è quasi lì, ci sono solo alcune cose per renderlo più Pythonic.

Per uno, non è necessario ottenere la lunghezza dell'elenco. Le liste vuote in Python vengono valutate come false in un controllo if. Dillo semplicemente

if list:

Inoltre, è una pessima idea da assegnare a variabili che si sovrappongono a parole riservate. "elenco" è una parola riservata in Python.

Quindi cambiamo questo in

some_list = get_list()
if some_list:

Un punto davvero importante che mancano molte soluzioni qui è che tutte le funzioni / i metodi Python restituiscono None per impostazione predefinita . Prova quanto segue.

def does_nothing():
    pass

foo = does_nothing()
print foo

A meno che non sia necessario restituire None per terminare anticipatamente una funzione, non è necessario restituire esplicitamente None. Abbastanza sinteticamente, basta restituire la prima voce, se dovesse esistere.

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

E infine, forse questo era implicito, ma solo per essere esplicito (perché esplicito è meglio che implicito ), non dovresti avere la tua funzione ottenere l'elenco da un'altra funzione; basta passarlo come parametro. Quindi, il risultato finale sarebbe

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

my_list = get_list()
first_item = get_first_item(my_list)

Come ho detto, l'OP era quasi lì, e solo pochi tocchi gli danno il sapore Python che stai cercando.


1
Perché 'list' è una parola riservata in Python? O, più precisamente, dove dice che 'list' è una parola reserver in Python? O, più precisamente, come hai verificato che 'list' è una parola riservata in Python? Considerando che posso dichiarare una variabile chiamata 'list', chiaramente non è una parola riservata.
Lasse V. Karlsen,

2
Punto preso. Le parole veramente riservate in Python sono disponibili qui tinyurl.com/424663 Tuttavia, è una buona idea considerare riservate funzioni e tipi predefiniti. list () in realtà genera un elenco. Lo stesso vale per dict, range, ecc.
gotgenes,

Combinerei le ultime due righe, eliminando la variabile temporanea (a meno che tu non abbia bisogno di my_list). Ciò migliora la leggibilità.
Svante,

Questa è praticamente la risposta a cui stavo gravitando.
Robert Rossney,

1
get_first_itemdovrebbe accettare un defaultparametro (come dict.get) che si imposta automaticamente su Nessuno. Pertanto l'interfaccia di funzione comunica esplicitamente cosa succede se my_listè vuota.
jfs,

3
for item in get_list():
    return item

Questo è succinto e bello.
gotgenes,

Tragicamente, non funziona; genera un'eccezione "TypeError: 'NoneType' non è iterabile" se get_list () restituisce None.
Robert Rossney,

Per risolvere: "per l'elemento in get_list () o []:"
aggiunto il

5
La domanda presume chiaramente che get_list restituisca un elenco. len e getitem sono chiamati al suo risultato.
A. Coady,

2

Francamente, non credo che esista un linguaggio migliore: il tuo è chiaro e conciso - non c'è bisogno di qualcosa di "migliore". Forse, ma questa è davvero una questione di gusti, potresti cambiare if len(list) > 0:con if list:- un elenco vuoto valuterà sempre Falso.

Su una nota correlata, Python non è Perl (nessun gioco di parole previsto!), Non è necessario ottenere il codice più cool possibile.
In realtà, il codice peggiore che abbia mai visto in Python, è stato anche molto bello :-) e completamente non mantenibile.

A proposito, la maggior parte della soluzione che ho visto qui non prende in considerazione quando list [0] restituisce False (es. Stringa vuota o zero) - in questo caso, tutti restituiscono None e non l'elemento corretto.


In Python, è considerato un buon stile scrivere if lst:piuttosto che if len(lst) > 0:. Inoltre, non utilizzare le parole chiave Python come nomi di variabili; finisce in lacrime. È comune utilizzare Lo lstcome nome della variabile per alcuni elenchi. Sono sicuro che in questo caso non intendevi suggerire di utilizzare effettivamente listcome nome di variabile, intendevi semplicemente "un elenco". E +1 per "quando lst [0] viene valutato come falso".
Steveha,

Sì, stavo solo mantenendo lo stesso tipo di nome di OP, per una migliore chiarezza. Ma hai decisamente ragione: bisogna sempre fare attenzione a non sostituire le parole chiave di Python.
rapina il

2

Idioma Python per restituire il primo oggetto o Nessuno?

L'approccio più Pythonic è quello che ha dimostrato la risposta più votata, ed è stata la prima cosa che mi è venuta in mente quando ho letto la domanda. Ecco come usarlo, prima se l'elenco eventualmente vuoto viene passato in una funzione:

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

E se l'elenco viene restituito da una get_listfunzione:

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

Altri modi hanno dimostrato di farlo qui, con spiegazioni

for

Quando ho iniziato a provare a pensare a modi intelligenti per farlo, questa è la seconda cosa a cui ho pensato:

for item in get_list():
    return item

Ciò presume che la funzione finisca qui, restituendo implicitamente Nonese get_listrestituisce un elenco vuoto. Il seguente codice esplicito è esattamente equivalente:

for item in get_list():
    return item
return None

if some_list

È stato anche proposto quanto segue (ho corretto il nome della variabile errato) che utilizza anche l'implicito None. Ciò sarebbe preferibile a quanto sopra, in quanto utilizza il controllo logico anziché un'iterazione che potrebbe non verificarsi. Questo dovrebbe essere più facile da capire immediatamente cosa sta succedendo. Ma se stiamo scrivendo per leggibilità e manutenibilità, dovremmo anche aggiungere l'esplicito return Nonealla fine:

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

fetta or [None]e selezionare zeroth indice

Questa è anche la risposta più votata:

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

La sezione non è necessaria e crea un ulteriore elenco di un elemento in memoria. Quanto segue dovrebbe essere più performante. Per spiegare, orrestituisce il secondo elemento se il primo si trova Falsein un contesto booleano, quindi se get_listrestituisce un elenco vuoto, l'espressione contenuta tra parentesi restituirà un elenco con "Nessuno", a cui accederà quindi l' 0indice:

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

Il prossimo utilizza il fatto che restituisce il secondo elemento se il primo si trova Truein un contesto booleano e, poiché fa riferimento a my_list due volte, non è migliore dell'espressione ternaria (e tecnicamente non è una riga):

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

next

Quindi abbiamo il seguente uso intelligente del builtin nexteiter

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

Per spiegare, iterrestituisce un iteratore con un .nextmetodo. ( .__next__in Python 3.) Quindi l'integrato nextchiama quel .nextmetodo e, se l'iteratore è esaurito, restituisce il valore predefinito che diamo None.

espressione ternaria ridondante ( a if b else c) e ritorno indietro

È stato proposto quanto segue, ma sarebbe preferibile l'inverso, poiché la logica è generalmente meglio compresa in positivo anziché in negativo. Dato che get_listviene chiamato due volte, a meno che il risultato non sia memorizzato in qualche modo, questo avrebbe prestazioni scarse:

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

Il migliore inverso:

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

Ancora meglio, usa una variabile locale in modo che get_listvenga chiamata solo una volta e hai discusso per la prima volta la soluzione Pythonic consigliata:

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

2

Per quanto riguarda gli idiomi, esiste una ricetta itertools chiamata nth.

Dalle ricette itertools:

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

Se desideri una linea, considera l'installazione di una libreria che implementa questa ricetta per te, ad esempio more_itertools:

import more_itertools as mit

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

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

È disponibile un altro strumento che restituisce solo il primo elemento, chiamato more_itertools.first.

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

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

Questi itertools scalano genericamente per qualsiasi iterabile, non solo per gli elenchi.



1

Per curiosità, ho gestito i tempi su due delle soluzioni. La soluzione che utilizza un'istruzione return per terminare prematuramente un ciclo for è leggermente più costosa sulla mia macchina con Python 2.5.1, sospetto che ciò abbia a che fare con l'impostazione dell'iterabile.

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

Questi sono i tempi che ho avuto:

empty_lists:
  index_first_item:
    0.748353 secondi
    0.741086 secondi
    0.741191 secondi
    0,743543 secondi media
  return_first_item:
    0,785511 secondi
    0,822178 secondi
    0.782846 secondi
    0,796845 secondi media
full_lists:
  index_first_item:
    0.762618 secondi
    0.788040 secondi
    0,786849 secondi
    0,779169 secondi in media.
  return_first_item:
    0.802735 secondi
    0,878706 secondi
    0.808781 secondi
    0,830074 secondi media
mixed_lists:
  index_first_item:
    0.791129 secondi
    0,743526 secondi
    0.744441 secondi
    0,759699 secondi avg.
  return_first_item:
    0.784801 secondi
    0.785146 secondi
    0,840193 secondi
    0.803380 secondi avg.

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

1
Le eccezioni generalmente non vengono utilizzate per il controllo del flusso.
Matt Green,

Non credo che allungare questo codice e aggiungere la gestione delle eccezioni sia esattamente il linguaggio che stavo cercando.
Robert Rossney,

3
Ma questo è un linguaggio comune di Python! Per motivi di prestazioni, potresti non utilizzarlo; otterrai prestazioni migliori if len(a) > 0:ma questo è considerato un buon stile. Nota quanto bene questo esprima il problema: "prova a estrarre la testa di un elenco, e se ciò non funziona, ritorna None". Cerca su Google "EAFP" o guardare qui: python.net/~goodger/projects/pycon/2007/idiomatic/…
steveha

@steveha Dipende dai tuoi dati. Se len (a) è in genere> 0 e len (a) <1 è uno stato raro, la cattura dell'eccezione sarà effettivamente più performante.
limscoder,

@limscoder, sono d'accordo con te. Non sono entrato nei dettagli di quando potresti non usarlo per motivi di prestazioni; Non ho detto che non lo avresti mai usato.
Steveha,

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

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

A proposito: rielaborerei il flusso del tuo programma generale in qualcosa del genere:

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

(Evitare la ripetizione quando possibile)


0

Cosa ne pensi di questo:

(my_list and my_list[0]) or None

Nota: questo dovrebbe funzionare bene per gli elenchi di oggetti ma potrebbe restituire una risposta errata in caso di elenco di numeri o stringhe per i commenti seguenti.


Che dire ([0,1] and 0) or None!
Kenly,

Questo è un buon punto ... Non sicuro da usare con le raccolte di numeri nella forma attuale :(
VitalyB

Stessa cosa con (['','A'] and '') or None.
Kenly,

Se my_list[0]viene valutato come False, il risultato dovrebbe essere None.
Kenly,

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

0

Non sono sicuro di quanto sia pythonic ma fino a quando non c'è una prima funzione nella libreria la includo nel sorgente:

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

È solo una riga (conforme al nero) ed evita le dipendenze.



-1

Probabilmente non è la soluzione più veloce, ma nessuno ha menzionato questa opzione:

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

se get_list()può tornare Nonepuoi usare:

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

vantaggi:

-una linea

-hai appena chiamato get_list()una volta

-facile da capire


-1

Il mio caso d'uso era solo quello di impostare il valore di una variabile locale.

Personalmente ho trovato il metodo più pulito e meno pulito da leggere

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

che tagliare un elenco.

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

-1

Diverse persone hanno suggerito di fare qualcosa del genere:

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

Funziona in molti casi, ma funzionerà solo se list [0] non è uguale a 0, False o una stringa vuota. Se list [0] è 0, False o una stringa vuota, il metodo restituirà erroneamente None.

Ho creato questo bug nel mio codice troppe volte!


3
Questo dovrebbe essere un commento sotto le risposte imperfette piuttosto che una risposta autonoma.
IJ Kennedy,

-2

È possibile utilizzare il metodo di estrazione . In altre parole, estrai quel codice in un metodo che poi chiameresti.

Non proverei a comprimerlo molto di più, i liner sembrano più difficili da leggere rispetto alla versione dettagliata. E se usi il metodo Extract, è una riga;)



-3

non è il pitone idiomatico equivalente agli operatori ternari in stile C.

cond and true_expr or false_expr

vale a dire.

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

Se uno [0] è il numero 0 o la stringa vuota (o qualsiasi altra cosa che risulti falsa), questo restituirà None invece di ciò che effettivamente è uno [0].
Adam Rosenfield,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.