Idiome Python pour retourner le premier élément ou aucun


275

Je suis sûr qu'il existe un moyen plus simple de le faire qui ne m'est tout simplement pas venu à l'esprit.

J'appelle un tas de méthodes qui renvoient une liste. La liste est peut-être vide. Si la liste n'est pas vide, je veux retourner le premier élément; sinon, je veux retourner Aucun. Ce code fonctionne:

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

Il me semble qu'il devrait y avoir un idiome d'une seule ligne pour faire cela, mais pour la vie de moi, je ne peux pas y penser. Y a-t-il?

Éditer:

La raison pour laquelle je recherche une expression sur une ligne ici n'est pas que j'aime le code incroyablement concis, mais parce que je dois écrire beaucoup de code comme ceci:

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

Ce que j'aimerais faire peut certainement être accompli avec une fonction (et le sera probablement):

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

J'ai posé la question parce que je suis souvent surpris par ce que les expressions simples en Python peuvent faire, et je pensais que l'écriture d'une fonction était une chose stupide à faire si une expression simple pouvait faire l'affaire. Mais en voyant ces réponses, il semble qu'une fonction soit la solution simple.


28
btw, sur Python 2.6+, vous pouvez utiliser next(iter(your_list), None)au lieu de first_item(your_list)supposer que ce your_listn'est pas le cas None( get_first_list()et get_second_list()doit toujours renvoyer un itérable).
jfs

1
Je pense que vous voulez dire next(iter(your_list)), puisque si vous fournissez un deuxième argument iter, vous lui dites que le premier argument peut être appelé.
Robert Rossney

7
Non, Nonevoici le deuxième paramètre pour next(), non iter(). next()retourne son deuxième paramètre s'il est donné au lieu d'augmenter StopIterationsi your_listest vide.
jfs

La solution suggérée par @JFSebastian existe avec une question en double: stackoverflow.com/a/18533669/144408
shahjapan

Réponses:


205

Python 2.6+

next(iter(your_list), None)

Si your_listpeut être 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

Exemple:

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

Une autre option consiste à intégrer la fonction ci-dessus:

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

Pour éviter, breakvous pourriez écrire:

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

Où:

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

1
Cette dernière option est presque exactement ce que je recherche: elle est claire, elle fonctionne et elle ne nécessite pas que je définisse une nouvelle fonction. Je dirais «exactement» si la pause n'était pas nécessaire, car le risque de l'omettre n'est pas négligeable. Mais cette approche a l’anneau de la vérité.
Robert Rossney

Oh, je n'aime pas ça du tout. Si un élément de la liste évalue False, cette valeur est ignorée et remplacée. Si vous avez une chaîne vide ""dans la liste, elle est supprimée et remplacée par une liste vide []. Si vous avez un 0, également remplacé par []. Si vous en avez Falselà, également remplacé. Etc. Vous pourriez vous en tirer dans un cas particulier, mais c'est une mauvaise habitude à développer.
steveha

4
@steveha: bool(lst) nous indique s'il len(lst) > 0ne nous dit rien sur ce que les éléments lstcontiennent, par exemple, bool([False]) == True;l'expression [False] or []retourne [False], la même chose est pour [0] or [] elle retourne [0].
jfs

@RobertRossney: J'ai ajouté yield_first()pour éviter la breakdéclaration.
jfs

1
@ThomasAhle: les deux sont vrais. J'ai mis à jour la réponse pour fournir la solution pour les cas non-None.
jfs

218

La meilleure façon est la suivante:

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

Vous pouvez également le faire sur une seule ligne, mais il est beaucoup plus difficile à lire pour le programmeur:

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

Oups. J'aurais dû mentionner qu'il s'agit d'une application Python 2.4.
Robert Rossney

15
@Adam: le futur peut utilisernext(iter(your_list), None)
jfs

@JFSebastian next(iter(your_list))est en fait suffisant
Brad Johnson

13
@BradleagheJohnson: faux. Il lève StopIteration au lieu de revenir Nonecomme OP le demande. Essayez: next(iter([])).
jfs

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

Cela devrait fonctionner.

BTW Je n'ai pas utilisé la variable list, car cela écrase la list()fonction intégrée .

Edit: j'avais une version légèrement plus simple, mais incorrecte ici plus tôt.


4
C'est intelligent et fonctionne, mais je pense que "retourner foo [0] si foo else None" comme suggéré par efotinis est un peu plus facile à suivre pour la maintenance.
Jay

3
La question demandait une solution sur une seule ligne, j'ai donc écarté cela.
récursif

Aucune de ces expressions ne fonctionne si get_list () renvoie None; vous obtenez "TypeError: l'objet 'NoneType' n'est pas inscriptible." ou "TypeError: type (s) d'opérande non pris en charge pour +: 'NoneType' et 'list'."
Robert Rossney

4
Dans la question, il dit que les méthodes renvoient des listes, dont None n'est pas une. Donc, étant donné les exigences, je crois toujours que les deux fonctionnent.
récursif

Devrait fonctionner maintenant si get_list () renvoie None car il n'est plus indexé ou ajouté.
Robert

33

La manière la plus idiomatique en python est d'utiliser le next () sur un itérateur puisque list est itérable . tout comme ce que @JFSebastian a mis dans le commentaire du 13 décembre 2011.

next(iter(the_list), None)Cela renvoie None si the_listest vide. voir next () Python 2.6+

ou si vous savez à coup sûr the_listn'est pas vide:

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


1
Cela a l'avantage que vous n'avez pas à affecter votre liste à une variable comme return l[0] if l else Nonesi la liste était une libcomp. Lorsque la liste est en fait un genexpr, c'est encore mieux car vous n'avez pas à construire la liste entière comme dansnext(iter(x for x in range(10) if x % 2 == 0), None)
RubenLaguna

Si vous savez à coup sûr qu'il the_listn'est pas vide, vous écririez the_list[0].
kaya3

12

Si vous essayez de saisir la première chose (ou aucune) d'une compréhension de liste, vous pouvez passer à un générateur pour le faire comme:

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

Pro: fonctionne si blah n'est pas indexable Con: c'est une syntaxe inconnue. C'est utile pour pirater et filtrer des trucs en ipython.


11

La solution de l'OP est presque là, il y a juste quelques choses pour le rendre plus Pythonic.

D'une part, il n'est pas nécessaire d'obtenir la longueur de la liste. Les listes vides en Python sont évaluées à False dans une vérification if. Dites simplement

if list:

De plus, c'est une très mauvaise idée à attribuer à des variables qui se chevauchent avec des mots réservés. "list" est un mot réservé en Python.

Alors changeons cela en

some_list = get_list()
if some_list:

Un point très important que beaucoup de solutions manquent ici est que toutes les fonctions / méthodes Python renvoient None par défaut . Essayez ce qui suit ci-dessous.

def does_nothing():
    pass

foo = does_nothing()
print foo

Sauf si vous devez retourner None pour terminer une fonction plus tôt, il n'est pas nécessaire de retourner explicitement None. Très succinctement, renvoyez simplement la première entrée, si elle existe.

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

Et finalement, c'était peut-être implicite, mais juste pour être explicite (car explicite vaut mieux qu'implicite ), vous ne devriez pas demander à votre fonction de récupérer la liste d'une autre fonction; il suffit de le passer en paramètre. Ainsi, le résultat final serait

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

my_list = get_list()
first_item = get_first_item(my_list)

Comme je l'ai dit, l'OP était presque là, et quelques touches lui donnent la saveur Python que vous recherchez.


1
Pourquoi «lister» un mot réservé en python? Ou, plus précisément, où est-il dit que «liste» est un mot réservé en python? Ou, plus précisément, comment avez-vous vérifié que «liste» est un mot réservé en python? Étant donné que je peux déclarer une variable nommée 'liste', ce n'est clairement pas un mot réservé.
Lasse V. Karlsen,

2
Point pris. Les mots vraiment réservés en Python peuvent être trouvés ici tinyurl.com/424663 Cependant, c'est une bonne idée de considérer les fonctions et types intégrés comme réservés. list () génère en fait une liste. Il en va de même pour dict, range, etc.
gotgenes

Je combinerais les deux dernières lignes, éliminant la variable temporaire (sauf si vous avez besoin de ma_liste plus loin). Cela améliore la lisibilité.
Svante

C'est à peu près la réponse vers laquelle je gravitais.
Robert Rossney

1
get_first_itemdevrait accepter un defaultparamètre (comme dict.get) qui se définit par défaut sur None. Ainsi, l'interface de fonction communique explicitement ce qui se passe si my_listest vide.
jfs

3
for item in get_list():
    return item

C'est succinct et beau.
gotgenes

Malheureusement, cela ne fonctionne pas; il lève une exception "TypeError: 'NoneType' n'est pas itérable" si get_list () renvoie None.
Robert Rossney

Pour corriger: "pour l'élément dans get_list () ou []:"
itsadok

5
La question suppose clairement que get_list renvoie une liste. len et getitem sont appelés sur son résultat.
A. Coady

2

Franchement, je ne pense pas qu'il y ait un meilleur idiome: votre est clair et concis - pas besoin de quelque chose de "meilleur". Peut-être, mais c'est vraiment une question de goût, vous pouvez changer if len(list) > 0:avec if list:- une liste vide sera toujours évaluée à Faux.

Sur une note connexe, Python n'est pas Perl (sans jeu de mots!), Vous n'avez pas à obtenir le code le plus cool possible.
En fait, le pire code que j'ai vu en Python, était aussi très cool :-) et complètement incontrôlable.

Soit dit en passant, la plupart des solutions que j'ai vues ici ne prennent pas en compte lorsque list [0] est évalué à False (par exemple une chaîne vide ou zéro) - dans ce cas, ils renvoient tous None et pas l'élément correct.


En Python, il est considéré comme un bon style d'écrire if lst:plutôt que if len(lst) > 0:. N'utilisez pas non plus de mots clés Python comme noms de variables; ça finit en larmes. Il est courant d'utiliser Lou lstcomme nom de variable pour une liste. Je suis sûr que dans ce cas, vous ne vouliez pas suggérer d'utiliser réellement listcomme nom de variable, vous vouliez simplement dire "une liste". Et, +1 pour "lorsque lst [0] est évalué à False".
steveha

Oui, je maintenais juste le même genre de nom d'OP, pour plus de clarté. Mais vous avez certainement raison: il faut toujours veiller à ne pas remplacer les mots clés Python.
voler

2

Idiome Python pour retourner le premier élément ou aucun?

L'approche la plus pythonique est ce que la réponse la plus votée a démontré, et c'était la première chose qui m'est venue à l'esprit lorsque j'ai lu la question. Voici comment l'utiliser, d'abord si la liste éventuellement vide est passée dans une fonction:

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

Et si la liste est renvoyée par une get_listfonction:

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

D'autres façons ont démontré de le faire ici, avec des explications

for

Quand j'ai commencé à essayer de trouver des moyens intelligents de le faire, c'est la deuxième chose à laquelle j'ai pensé:

for item in get_list():
    return item

Cela suppose que la fonction se termine ici, renvoyant implicitement Nonesi get_listrenvoie une liste vide. Le code explicite ci-dessous est exactement équivalent:

for item in get_list():
    return item
return None

if some_list

Ce qui suit a également été proposé (j'ai corrigé le nom de variable incorrect) qui utilise également l'implicite None. Ce serait préférable à ce qui précède, car il utilise la vérification logique au lieu d'une itération qui peut ne pas se produire. Cela devrait être plus facile à comprendre immédiatement ce qui se passe. Mais si nous écrivons pour la lisibilité et la maintenabilité, nous devons également ajouter l'explicite return Noneà la fin:

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

couper or [None]et sélectionner l'index zeroth

Celui-ci est également dans la réponse la plus votée:

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

La tranche n'est pas nécessaire et crée une liste supplémentaire d'un élément en mémoire. Les éléments suivants devraient être plus performants. Pour expliquer, orretourne le deuxième élément si le premier est Falsedans un contexte booléen, donc si get_listretourne une liste vide, l'expression contenue dans les parenthèses retournera une liste avec 'None', qui sera ensuite accessible par l' 0index:

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

Le suivant utilise le fait que et retourne le deuxième élément si le premier est Truedans un contexte booléen, et comme il fait référence à my_list deux fois, ce n'est pas mieux que l'expression ternaire (et techniquement pas une ligne):

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

next

Ensuite, nous avons l'utilisation intelligente suivante de la fonction intégrée nextetiter

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

Pour expliquer, iterrenvoie un itérateur avec une .nextméthode. ( .__next__En Python 3.) Ensuite , les BUILTIN nextappels .nextméthode, et si le iterator est épuisé, retourne la valeur par défaut que nous donnons, None.

expression ternaire redondante ( a if b else c) et retour en arrière

Ce qui suit a été proposé, mais l'inverse serait préférable, car la logique est généralement mieux comprise dans le positif plutôt que dans le négatif. Puisque get_listest appelé deux fois, à moins que le résultat ne soit mémorisé d'une manière ou d'une autre, cela fonctionnerait mal:

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

Le meilleur inverse:

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

Encore mieux, utilisez une variable locale pour qu'elle get_listne soit appelée qu'une seule fois, et vous avez d'abord discuté de la solution Pythonic recommandée:

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

2

En ce qui concerne les idiomes, il existe une recette itertools appelée nth.

Des recettes d'itertools:

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

Si vous voulez une ligne, envisagez d'installer une bibliothèque qui implémente cette recette pour vous, par exemple more_itertools:

import more_itertools as mit

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

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

Un autre outil est disponible qui ne renvoie que le premier élément, appelé more_itertools.first.

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

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

Ces outils sont évolutifs de manière générique pour tout itérable, pas seulement pour les listes.



1

Par curiosité, j'ai exécuté des timings sur deux des solutions. La solution qui utilise une déclaration de retour pour terminer prématurément une boucle for est légèrement plus coûteuse sur ma machine avec Python 2.5.1, je soupçonne que cela a à voir avec la configuration de l'itérable.

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

Voici les horaires que j'ai obtenus:

listes_vides:
  index_first_item:
    0,748353 secondes
    0,741086 secondes
    0,741191 secondes
    0,743543 secondes en moyenne
  return_first_item:
    0.785511 secondes
    0.822178 secondes
    0,782846 secondes
    0,796845 secondes av.
listes_complètes:
  index_first_item:
    0,762618 secondes
    0,788040 secondes
    0,786849 secondes
    0,779169 secondes av.
  return_first_item:
    0,802735 secondes
    0,878706 seconde
    0,808781 secondes
    0,830074 secondes moy.
listes_mixes:
  index_first_item:
    0.791129 secondes
    0,743526 secondes
    0,744441 secondes
    0,759699 secondes moy.
  return_first_item:
    0.784801 secondes
    0.785146 secondes
    0.840193 secondes
    0,803380 secondes en moyenne

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

1
Les exceptions ne sont généralement pas utilisées pour le contrôle de flux.
Matt Green

Je ne pense pas que rendre ce code plus long et y ajouter une gestion des exceptions soit exactement l'idiome que je cherchais.
Robert Rossney

3
Mais c'est un idiome Python commun! Pour des raisons de performances, vous pourriez ne pas l'utiliser; vous obtiendrez de meilleures performances, if len(a) > 0:mais cela est considéré comme un bon style. Notez à quel point cela exprime bien le problème: "essayez d'extraire la tête d'une liste, et si cela ne fonctionne pas, revenez None". Recherchez "EAFP" sur Google ou regardez ici: python.net/~goodger/projects/pycon/2007/idiomatic/…
steveha

@steveha Cela dépend de vos données. Si len (a) est généralement> 0 et len ​​(a) <1 est un état rare, intercepter l'exception sera en fait plus performant.
limscoder

@limscoder, je suis d'accord avec vous. Je ne suis simplement pas entré dans les détails du moment où vous ne pourriez pas l'utiliser pour des raisons de performances; Je n'ai pas dit que tu n'utiliserais jamais ça.
steveha

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

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

BTW: Je retravaillerais votre flux de programme général en quelque chose comme ceci:

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

(Éviter la répétition autant que possible)


0

Que dis-tu de ça:

(my_list and my_list[0]) or None

Remarque: Cela devrait fonctionner correctement pour les listes d'objets, mais cela pourrait retourner une réponse incorrecte en cas de nombre ou de liste de chaînes selon les commentaires ci-dessous.


Qu'en est-il ([0,1] and 0) or None!.
Kenly

C'est un bon point ... dangereux à utiliser avec les collections de nombres sous la forme actuelle :(
VitalyB

Même chose avec (['','A'] and '') or None.
Kenly

Si my_list[0]est évalué comme False, le résultat devrait être None.
Kenly

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

0

Je ne sais pas à quel point c'est pythonique, mais jusqu'à ce qu'il y ait une première fonction dans la bibliothèque, je l'inclus dans la source:

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

C'est juste une ligne (conforme au noir) et évite les dépendances.



-1

Probablement pas la solution la plus rapide, mais personne n'a mentionné cette option:

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

si get_list()peut revenir, Nonevous pouvez utiliser:

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

Avantages:

-une ligne

-Tu appelles juste get_list()une fois

-facile à comprendre


-1

Mon cas d'utilisation consistait uniquement à définir la valeur d'une variable locale.

Personnellement j'ai trouvé l'essayeur et le style à lire

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

que de trancher une liste.

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

-1

Plusieurs personnes ont suggéré de faire quelque chose comme ceci:

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

Cela fonctionne dans de nombreux cas, mais cela ne fonctionnera que si la liste [0] n'est pas égale à 0, False ou une chaîne vide. Si la liste [0] est 0, False ou une chaîne vide, la méthode ne renvoie pas correctement None.

J'ai créé ce bug trop souvent dans mon propre code!


3
Cela devrait être un commentaire sous la ou les réponses erronées plutôt qu'une réponse autonome.
IJ Kennedy

-2

Vous pouvez utiliser la méthode d'extraction . En d'autres termes, extrayez ce code dans une méthode que vous appelleriez ensuite.

Je n'essaierais pas de le compresser beaucoup plus, les doublures un semblent plus difficiles à lire que la version verbeuse. Et si vous utilisez la méthode d'extraction, c'est une doublure;)



-3

n'est pas le python idiomatique équivalent aux opérateurs ternaires de style C

cond and true_expr or false_expr

c'est à dire.

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

Si un [0] est le nombre 0 ou la chaîne vide (ou toute autre chose qui vaut false), cela renverra None au lieu de ce qu'est réellement un [0].
Adam Rosenfield,
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.