Modismo de Python para devolver el primer elemento o Ninguno
-
21-08-2019 - |
Pregunta
Estoy seguro de que hay una forma más sencilla de hacer esto que simplemente no se me ocurre.
Estoy llamando a un montón de métodos que devuelven una lista.La lista puede estar vacía.Si la lista no está vacía, quiero devolver el primer elemento;de lo contrario, quiero devolver Ninguno.Este código funciona:
my_list = get_list()
if len(my_list) > 0: return my_list[0]
return None
Me parece que debería haber un modismo simple de una línea para hacer esto, pero por mi vida no puedo pensar en eso.¿Está ahí?
Editar:
La razón por la que estoy buscando una expresión de una línea aquí no es porque me guste el código increíblemente conciso, sino porque tengo que escribir mucho código como este:
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
Lo que me gustaría hacer ciertamente se puede lograr con una función (y probablemente lo será):
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
Publiqué la pregunta porque con frecuencia me sorprende lo que pueden hacer las expresiones simples en Python, y pensé que escribir una función era una tontería si había una expresión simple que pudiera funcionar.Pero al ver estas respuestas, parece una función. es la solución sencilla.
Solución
Python 2.6 +
next(iter(your_list), None)
Si your_list
puede ser 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
Ejemplo:
x = get_first(get_first_list())
if x:
...
y = get_first(get_second_list())
if y:
...
Otra opción es inline la función anterior:
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
Para evitar break
se podría escribir:
for x in yield_first(get_first_list()):
x # process x
for y in yield_first(get_second_list()):
y # process y
Donde:
def yield_first(iterable):
for item in iterable or []:
yield item
return
Otros consejos
La mejor forma de hacerlo es la siguiente:
a = get_list()
return a[0] if a else None
También puede hacerlo en una sola línea, pero es mucho más difícil para el programador para leer:
return (get_list()[:1] or [None])[0]
(get_list() or [None])[0]
Eso debería funcionar.
Por cierto yo no usar la variable list
, ya que sobrescribe la función incorporada list()
.
Editar:. Tenía una versión ligeramente más simple, pero mal aquí anteriormente
La forma más pitón idiomática es utilizar la siguiente () en un iterador ya está lista iterables . al igual que lo @ J.F.Sebastian poner en el comentario el 13 dic, 2011.
next(iter(the_list), None)
Esto devuelve Ninguno si the_list
está vacía. ver next () Python 2.6+
o si conoce a ciencia cierta iter(the_list).next()
no está vacío:
<=> ver iterator.next () Python 2.2 +
La solución de la OP es casi allí, hay sólo unas pocas cosas para que sea más Pythonic.
Por un lado, no hay necesidad para obtener la longitud de la lista. listas vacías en Python evalúan como False si en un cheque. Simplemente decir
if list:
Además, es una idea muy mala para asignar a las variables que se superponen con las palabras reservadas. "Lista" es una palabra reservada en Python.
Así que vamos a cambiar a
some_list = get_list()
if some_list:
Un punto muy importante que una gran cantidad de soluciones que aquí se pierda es que todas las funciones de Python / métodos devuelven Ninguno de forma predeterminada . Pruebe lo siguiente a continuación.
def does_nothing():
pass
foo = does_nothing()
print foo
A menos que necesite volver Ninguno de interrumpir una función temprana, es innecesario volver explícitamente Ninguno. Muy sucintamente, vuelve la primera entrada, en caso de existir.
some_list = get_list()
if some_list:
return list[0]
Y, por último, tal vez esto está implícito, pero sólo para ser explícita (porque explícita es mejor que implícita), que no debería tener su función de obtener la lista de otra función; simplemente pasarlo como parámetro. Por lo tanto, el resultado final sería
def get_first_item(some_list):
if some_list:
return list[0]
my_list = get_list()
first_item = get_first_item(my_list)
Como ya he dicho, el PO estaba casi allí, y sólo unos toques que dan el sabor de Python que esté buscando.
Si usted se encuentra tratando de arrancar lo primero (o ninguno) a partir de una lista por comprensión puede cambiar a un generador que hacerlo así:
next((x for x in blah if cond), None)
Pro: funciona si no es bla Con indexable: es la sintaxis desconocida. Es útil, mientras que la piratería y todo lo demás filtrado en ipython sin embargo.
for item in get_list():
return item
Francamente, no creo que hay una mejor expresión idiomática: su clara y concisa es - no hay necesidad de nada "mejor". Tal vez, pero esto es realmente una cuestión de gusto, puede cambiar if len(list) > 0:
con if list:
-. Una lista vacía siempre evaluará en False
En una nota relacionada, Python es no Perl (sin juego de palabras!), Que no tiene que obtener el código más fresco posible.
En realidad, la peor que he visto código en Python, también era muy fresco :-) y completamente imposible de mantener.
Por cierto, la mayor parte de la solución que he visto aquí no tienen en cuenta a la hora de lista [0] se evalúa como falsa (por ejemplo, cadena vacía, o cero) - en este caso, todos vuelven Ninguno y no el elemento correcto .
¿Idioma de Python para devolver el primer elemento o ninguno?
El enfoque más pitónico es lo que demostró la respuesta más votada, y fue lo primero que me vino a la mente cuando leí la pregunta.Aquí se explica cómo usarlo, primero si la lista posiblemente vacía se pasa a una función:
def get_first(l):
return l[0] if l else None
Y si la lista se devuelve de un get_list
función:
l = get_list()
return l[0] if l else None
Otras formas demostradas de hacer esto aquí, con explicaciones.
for
Cuando comencé a pensar en formas inteligentes de hacer esto, esto fue lo segundo que pensé:
for item in get_list():
return item
Esto supone que la función termina aquí, devolviendo implícitamente None
si get_list
devuelve una lista vacía.El siguiente código explícito es exactamente equivalente:
for item in get_list():
return item
return None
if some_list
También se propuso lo siguiente (corregí el nombre de variable incorrecto) que también usa el implícito None
.Esto sería preferible a lo anterior, ya que utiliza la verificación lógica en lugar de una iteración que puede no ocurrir.Esto debería ser más fácil de entender inmediatamente lo que está sucediendo.Pero si escribimos para facilitar la lectura y el mantenimiento, también deberíamos agregar el explícito return None
al final:
some_list = get_list()
if some_list:
return some_list[0]
rebanada or [None]
y seleccione el índice cero
Esta también está en la respuesta más votada:
return (get_list()[:1] or [None])[0]
El segmento es innecesario y crea una lista adicional de un elemento en la memoria.Lo siguiente debería tener más rendimiento.Para explicar, or
devuelve el segundo elemento si el primero es False
en un contexto booleano, entonces si get_list
devuelve una lista vacía, la expresión contenida entre paréntesis devolverá una lista con 'Ninguno', a la que luego accederá el 0
índice:
return (get_list() or [None])[0]
El siguiente usa el hecho de que y devuelve el segundo elemento si el primero es True
en un contexto booleano, y dado que hace referencia a my_list dos veces, no es mejor que la expresión ternaria (y técnicamente no es una sola línea):
my_list = get_list()
return (my_list and my_list[0]) or None
next
Entonces tenemos el siguiente uso inteligente del incorporado next
y iter
return next(iter(get_list()), None)
Para explicar, iter
devuelve un iterador con un .next
método.(.__next__
en Python 3.) Entonces el incorporado next
llama eso .next
método, y si el iterador se agota, devuelve el valor predeterminado que damos, None
.
expresión ternaria redundante (a if b else c
) y dando vueltas de regreso
Se propuso lo siguiente, pero sería preferible lo contrario, ya que la lógica suele entenderse mejor en lo positivo que en lo negativo.Desde get_list
se llama dos veces, a menos que el resultado se memorice de alguna manera, esto funcionaría mal:
return None if not get_list() else get_list()[0]
La mejor inversa:
return get_list()[0] if get_list() else None
Aún mejor, use una variable local para que get_list
solo se llama una vez y primero se analiza la solución Pythonic recomendada:
l = get_list()
return l[0] if l else None
En cuanto a los idiomas, hay un itertools receta llama nth
.
De itertools recetas:
def nth(iterable, n, default=None):
"Returns the nth item or a default value"
return next(islice(iterable, n, None), default)
Si desea una sola línea, considere la instalación de una biblioteca que implementa esta receta para que, por ejemplo, more_itertools
:
import more_itertools as mit
mit.nth([3, 2, 1], 0)
# 3
mit.nth([], 0) # default is `None`
# None
Otra herramienta que está disponible sólo devuelve el primer elemento, llamado more_itertools.first
.
mit.first([3, 2, 1])
# 3
mit.first([], default=None)
# None
Estos itertools escala genéricamente para cualquier iterable, no sólo para las listas.
Por curiosidad, me encontré con los tiempos en dos de las soluciones. La solución que utiliza una instrucción de retorno a terminar prematuramente un bucle for es un poco más costoso en mi máquina con Python 2.5.1, Sospecho que esto tiene que ver con la creación de la iterable.
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
Estos son los tiempos que tengo:
empty_lists: index_first_item: 0.748353 seconds 0.741086 seconds 0.741191 seconds 0.743543 seconds avg. return_first_item: 0.785511 seconds 0.822178 seconds 0.782846 seconds 0.796845 seconds avg. full_lists: index_first_item: 0.762618 seconds 0.788040 seconds 0.786849 seconds 0.779169 seconds avg. return_first_item: 0.802735 seconds 0.878706 seconds 0.808781 seconds 0.830074 seconds avg. mixed_lists: index_first_item: 0.791129 seconds 0.743526 seconds 0.744441 seconds 0.759699 seconds avg. return_first_item: 0.784801 seconds 0.785146 seconds 0.840193 seconds 0.803380 seconds avg.
try:
return a[0]
except IndexError:
return None
def head(iterable):
try:
return iter(iterable).next()
except StopIteration:
return None
print head(xrange(42, 1000) # 42
print head([]) # None
BTW: Me Rehago su flujo general del programa en algo como esto:
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))
(evitando la repetición siempre que sea posible)
¿Qué tal esto:
(my_list and my_list[0]) or None
Nota: Esto debería funcionar bien para las listas de objetos pero podría volver respuesta incorrecta en el caso de número o lista de cadenas por los comentarios a continuación
.Se puede usar Extraer método . En otras palabras extraer ese código en un método que desea llamar entonces.
No intentaría comprimirlo mucho más, los trazadores de líneas uno parecen difíciles de leer que la versión prolija. Y si utiliza Extraer método, que es un chiste;)
Usando el truco and-or:
a = get_list()
return a and a[0] or None
Y qué decir de: next(iter(get_list()), None)
?
Puede que no sea el más rápido aquí, pero es estándar (a partir de Python 2.6) y sucinta.
Probablemente no es la solución más rápida, pero nadie mencionó esta opción:
dict(enumerate(get_list())).get(0)
Si get_list()
puede volver None
puede usar:
dict(enumerate(get_list() or [])).get(0)
Ventajas:
línea -ona
-usted a llamarlo <=> vez
Fácil de entender
Mi caso de uso sólo para establecer el valor de una variable local.
En lo personal he encontrado el try y except más limpio estilo para leer
items = [10, 20]
try: first_item = items[0]
except IndexError: first_item = None
print first_item
de rebanar una lista.
items = [10, 20]
first_item = (items[:1] or [None, ])[0]
print first_item
if mylist != []:
print(mylist[0])
else:
print(None)
Varias personas han sugerido hacer algo como esto:
list = get_list()
return list and list[0] or None
Esto funciona en muchos casos, pero sólo funcionará si la lista [0] no es igual a 0, o una cadena vacía Falso. Si la lista [0] es 0, falso, o una cadena vacía, el método incorrectamente volver Ninguno.
He creado este error en mi propio código demasiadas veces!
no es el pitón idiomática equivalente a de estilo C operadores ternarios
cond and true_expr or false_expr
ie.
list = get_list()
return list and list[0] or None