Je suis sûr qu'il existe un moyen plus simple de faire cela qui ne me vient tout simplement pas à l'esprit.
J'appelle un tas de méthodes qui renvoient une liste. La liste peut être vide. Si la liste n'est pas vide, je souhaite renvoyer le premier élément. sinon, je veux renvoyer 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 exister un langage simple à une ligne pour le faire, mais je ne peux pas y penser. Y a-t-il?
Edit:
La raison pour laquelle je recherche ici une expression d'une ligne n'est pas parce que j'aime un code extrêmement laconique, mais parce que je dois écrire beaucoup de code comme celui-ci:
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 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 posté la question parce que je suis souvent surpris par ce que des expressions simples dans Python peuvent faire, et je pensais qu'écrire une fonction était une chose stupide à faire s'il existait une simple expression qui pourrait faire l'affaire. Mais en voyant ces réponses, il semble qu'une fonction soit la solution simple.
next(iter(your_list), None)
Si your_list
peut être None
:
next(iter(your_list or []), None)
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 break
, vous pouvez é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
Le meilleur moyen est le suivant:
a = get_list()
return a[0] if a else None
Vous pouvez aussi le faire en une ligne, mais le programmeur a beaucoup de difficulté à lire:
return (get_list()[:1] or [None])[0]
(get_list() or [None])[0]
Cela devrait fonctionner.
BTW je n'ai pas utilisé la variable list
, car cela écrase la fonction intégrée list()
.
Edit: J'avais une version un peu plus simple, mais fausse ici plus tôt.
La manière la plus idiomatique de python consiste à utiliser le next () sur un itérateur puisque la liste est itérable . comme ce que @ J.F.Sebastian a mis dans le commentaire du 13 décembre 2011.
next(iter(the_list), None)
Ceci retourne None si the_list
est vide. voir next () Python 2.6 +
ou si vous êtes sûr que the_list
n'est pas vide:
iter(the_list).next()
see iterator.next () Python 2.2 +
La solution du PO est presque là, il y a juste quelques choses pour le rendre plus Pythonic.
D'une part, il n'est pas nécessaire de connaître la longueur de la liste. Les listes vides dans Python sont évaluées à False lors d'une vérification if. Dites simplement
if list:
De plus, c’est une très mauvaise idée d’affecter des variables qui se chevauchent avec des mots réservés. "liste" est un mot réservé en Python.
Alors changeons cela en
some_list = get_list()
if some_list:
Un point vraiment important que beaucoup de solutions manquent ici est que all Python fonctions/méthodes ne renvoie Aucun par défaut . Essayez ce qui suit ci-dessous.
def does_nothing():
pass
foo = does_nothing()
print foo
Sauf si vous devez renvoyer None pour mettre fin à une fonction plus tôt, il est inutile de renvoyer explicitement None. Assez succinctement, retournez simplement la première entrée, si elle existe.
some_list = get_list()
if some_list:
return list[0]
Et enfin, peut-être que cela était implicite, mais juste pour être explicite (parce que explicite vaut mieux que implicite ), vous ne devriez pas avoir votre fonction extraire la liste d'une autre fonction; il suffit de le passer en paramètre. Donc, 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 le disais, le PO était presque là et quelques touches lui donnent la saveur Python que vous recherchez.
Si vous essayez d’arracher la première chose (ou Aucune) à une liste de compréhension, vous pouvez passer à un générateur pour le faire comme ceci:
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 lors du piratage et du filtrage de choses dans ipython.
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’est la première chose qui m’est venue à l’esprit lorsque j'ai lu la question. Voici comment l'utiliser, tout d'abord si la liste éventuellement vide est passée à une fonction:
def get_first(l):
return l[0] if l else None
Et si la liste est renvoyée par une fonction get_list
:
l = get_list()
return l[0] if l else None
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 et renvoie implicitement None
si get_list
renvoie 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 le None
implicite. 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 des raisons de lisibilité et de maintenabilité, nous devrions également ajouter le return None
explicite à la fin:
some_list = get_list()
if some_list:
return some_list[0]
or [None]
et sélectionnez zeroth indexCelui-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. Ce qui suit devrait être plus performant. Pour expliquer, or
renvoie le deuxième élément si le premier est False
dans un contexte booléen. Ainsi, si get_list
renvoie une liste vide, l'expression contenue dans les parenthèses retournera une liste avec ' Aucune ', qui sera ensuite accessible par l'index 0
:
return (get_list() or [None])[0]
Le prochain utilise le fait que et renvoie le second élément si le premier est True
dans un contexte booléen, et puisqu'il référence deux fois à my_list, il ne vaut pas mieux que l'expression ternaire (et techniquement, ce n'est pas un one-liner) :
my_list = get_list()
return (my_list and my_list[0]) or None
next
Nous avons ensuite l’utilisation intelligente suivante des fonctions intégrées next
et iter
return next(iter(get_list()), None)
Pour expliquer, iter
renvoie un itérateur avec une méthode .next
. (.__next__
dans Python 3.) Ensuite, la fonction intégrée next
appelle cette méthode .next
et si l'itérateur est épuisé, renvoie la valeur par défaut que nous donnons, None
.
a if b else c
) et retourLa proposition ci-dessous a été proposée, mais l'inverse serait préférable, car la logique est généralement mieux comprise dans le positif que dans le négatif. Puisque get_list
est appelé deux fois, à moins que le résultat ne soit mémorisé d'une manière ou d'une autre, les performances seraient médiocres:
return None if not get_list() else get_list()[0]
Le meilleur inverse:
return get_list()[0] if get_list() else None
Mieux encore, utilisez une variable locale pour que get_list
ne soit appelé qu'une seule fois et que la solution Pythonic recommandée soit d'abord discutée:
l = get_list()
return l[0] if l else None
En ce qui concerne les idiomes, il existe une recette des outils informatiques appelée nth
.
De recettes 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 des solutions uniques, pensez à 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 renvoie uniquement le premier élément, appelé more_itertools.first
.
_mit.first([3, 2, 1])
# 3
mit.first([], default=None)
# None
_
Ces outils informatiques sont génériques pour tous les itératifs, pas seulement pour les listes.
Franchement, je ne pense pas qu’il existe un meilleur langage: votre langage est clair et concis - vous n’avez besoin de rien de "meilleur". Peut-être, mais c'est vraiment une question de goût, vous pourriez changer if len(list) > 0:
avec if list:
- une liste vide sera toujours évaluée à False.
Sur une note connexe, Python 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 également très cool :-) et totalement immuable.
En passant, la plupart des solutions que j'ai vues ici ne prennent pas en compte la liste False (0, par exemple, chaîne vide ou zéro). Dans ce cas, elles ne renvoient toutes ni aucun, ni l'élément correct.
for item in get_list():
return item
Par curiosité, j’ai programmé deux solutions. La solution qui utilise une instruction return pour mettre fin prématurément à une boucle for est légèrement plus coûteuse sur ma machine avec Python 2.5.1, je suppose 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 __== '__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
Ce sont les timings que j'ai eu:
empty_lists: index_first_item: 0.748353 secondes 0.741086 secondes 0.741191 secondes 0.743543 secondes moyen return_first_item : 0.785511 secondes 0.822178 secondes 0.782846 secondes 0.796845 secondes moyennes Listes complètes: Index_first_item: 0.762618 secondes 0.788040 secondes 0.786849 secondes 0.779169 secondes moyen Return_first_item: 0.802735 secondes 0.878706 secondes 0.808781 secondes 0.830074 secondes moyen Listes mixtes: Index_first_item: 0.791129 secondes 0.743526 secondes 0.744441 secondes 0,759699 secondes moyen Return_first_item: 0,784801 secondes 0.785146 secondes 0.840193 secondes 0.803380 secondes moyen
try:
return a[0]
except IndexError:
return None
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 list [0] n'est pas égal à 0, False ou une chaîne vide. Si list [0] est 0, False ou une chaîne vide, la méthode ne renverra pas correctement None.
J'ai créé ce bogue dans mon propre code une fois de trop!
Que dis-tu de ça:
(my_list and my_list[0]) or None
Note: Cela devrait fonctionner correctement pour les listes d'objets, mais cela pourrait donner une réponse incorrecte en cas de liste de nombres ou de chaînes selon les commentaires ci-dessous.
def head(iterable):
try:
return iter(iterable).next()
except StopIteration:
return None
print head(xrange(42, 1000) # 42
print head([]) # None
BTW: Je retravailler votre flux de programme général dans quelque chose comme:
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)