Idioma de Python para devolver el primer elemento o Ninguno


275

Estoy seguro de que hay una manera más simple 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 lenguaje simple de una línea para hacer esto, pero por mi vida no puedo pensar en eso. ¿Esta ahí?

Editar:

La razón por la que estoy buscando una expresión de una línea aquí no es que 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 sin duda 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 algo tonto si había una expresión simple que pudiera hacer el truco. Pero al ver estas respuestas, parece que una función es la solución simple.


28
por cierto, en Python 2.6+ podría usar en next(iter(your_list), None)lugar de first_item(your_list)asumir your_listque no lo es None( get_first_list()y get_second_list()siempre debe devolver un iterable).
jfs

1
Creo que quiere decir next(iter(your_list)), ya que si proporciona un segundo argumento para iter, le está diciendo que el primer argumento es invocable.
Robert Rossney

77
No, Noneaquí está el segundo parámetro para next(), no iter(). next()devuelve su segundo parámetro si se proporciona en lugar de aumentar StopIterationsi your_listestá vacío.
jfs

La solución sugerida por @JFSebastian existe con una pregunta duplicada: stackoverflow.com/a/18533669/144408
shahjapan

Respuestas:


205

Python 2.6+

next(iter(your_list), None)

Si your_listpuede 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 alinear 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 que breakpuedas escribir:

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

Dónde:

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

1
Esa última opción es casi exactamente lo que estoy buscando: está claro, funciona y no requiere que defina una nueva función. Yo diría "exactamente" si el descanso no fuera necesario, porque el riesgo de omitirlo no es insignificante. Pero este enfoque tiene el anillo de la verdad.
Robert Rossney

Oh, no me gusta esto en absoluto. Si algún elemento de la lista evalúa False, ese valor se descarta y se reemplaza. Si tiene una cadena vacía ""en la lista, se descarta y se reemplaza por una lista vacía []. Si tienes un 0, también reemplazado por []. Si tienes Falseallí, también reemplazado. Etc. Puede salirse con la suya en un caso específico, pero este es un mal hábito para desarrollar.
steveha

44
@steveha: bool(lst) nos dice si len(lst) > 0no nos dice nada sobre qué elementos lstcontiene, bool([False]) == True;por ejemplo, por lo tanto , la expresión [False] or []regresa [False], lo mismo es para [0] or [] que regrese [0].
jfs el

@RobertRossney: agregué yield_first()para evitar la breakdeclaración.
jfs

1
@ThomasAhle: ambas son ciertas. He actualizado la respuesta para proporcionar la solución para un caso que no sea Ninguno.
jfs

218

La mejor manera es esta:

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

También puede hacerlo en una línea, pero es mucho más difícil de leer para el programador:

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

Ups Debería haber mencionado que esta es una aplicación Python 2.4.
Robert Rossney

15
@Adam: el futuro puede usarnext(iter(your_list), None)
jfs

@JFSebastian en realidad next(iter(your_list))es suficiente
Brad Johnson

13
@BradleagheJohnson: mal. Eleva StopIteration en lugar de regresar Nonecomo OP pregunta. Proveedores: next(iter([])).
jfs

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

Eso debería funcionar.

Por cierto, no utilicé la variable list, porque eso sobrescribe la list()función incorporada .

Editar: tuve una versión un poco más simple, pero incorrecta aquí anteriormente.


44
Esto es inteligente y funciona, pero creo que "devolver foo [0] si no hay ninguno" como lo sugieren efotinis es un poco más fácil de seguir para el mantenimiento.
Jay

3
La pregunta era pedir una solución de una línea, así que lo descarté.
recursivo

Ninguna de esas expresiones funciona si get_list () devuelve None; obtienes "TypeError: el objeto 'NoneType' no se puede suscribir". o "TypeError: tipos de operando no admitidos para +: 'NoneType' y 'list'".
Robert Rossney

44
En la pregunta, dice que los métodos devuelven listas, de las cuales None no es una. Entonces, dados los requisitos, todavía creo que ambos funcionan.
recursivo

Debería funcionar ahora si get_list () devuelve None ya que ya no se suscribe o agrega.
Robert

33

La forma más idiomática de Python es usar next () en un iterador ya que la lista es iterable . justo como lo que @JFSebastian puso en el comentario el 13 de diciembre de 2011.

next(iter(the_list), None)Esto devuelve Ninguno si the_listestá vacío. ver siguiente () Python 2.6+

o si sabes con certeza the_listque no está vacío:

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


1
Esto tiene la ventaja de que no tiene que asignar su lista a una variable como return l[0] if l else Nonesi la lista fuera libcomp. Cuando la lista es en realidad un genexpr esto es aún mejor, ya que no tiene que construir toda la lista como ennext(iter(x for x in range(10) if x % 2 == 0), None)
RubenLaguna

Si sabe con certeza the_listque no está vacío, escribiría the_list[0].
kaya3

12

Si te encuentras tratando de extraer lo primero (o Ninguno) de una lista de comprensión, puedes cambiar a un generador para hacerlo como:

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

Pro: funciona si bla no es indexable Con: es una sintaxis desconocida. Sin embargo, es útil al piratear y filtrar cosas en ipython.


11

La solución del OP está casi allí, solo hay algunas cosas para hacerlo más Pythonic.

Por un lado, no es necesario obtener la longitud de la lista. Las listas vacías en Python evalúan a False en una comprobación if. Solo di

if list:

Además, es una muy mala idea asignar variables que se superponen con palabras reservadas. "lista" es una palabra reservada en Python.

Así que cambiemos eso a

some_list = get_list()
if some_list:

Un punto realmente importante que muchas de las soluciones aquí pasan por alto es que todas las funciones / métodos de Python no devuelven ninguno de forma predeterminada . Pruebe lo siguiente a continuación.

def does_nothing():
    pass

foo = does_nothing()
print foo

A menos que necesite devolver None para terminar una función antes, no es necesario devolver None explícitamente. Muy sucintamente, solo devuelva la primera entrada, en caso de que exista.

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

Y finalmente, tal vez esto estaba implícito, pero solo para ser explícito (porque explícito es mejor que implícito ), no debería hacer que su función obtenga la lista de otra función; solo pásalo como parámetro. Entonces, 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 dije, el OP estaba casi allí, y solo unos pocos toques le dan el sabor Python que estás buscando.


1
¿Por qué 'lista' es una palabra reservada en Python? O, más concretamente, ¿dónde dice que 'lista' es una palabra reserver en python? O, más concretamente, ¿cómo verificó que 'lista' es una palabra reservada en python? Considerando que puedo declarar una variable llamada 'lista', claramente no es una palabra reservada.
Lasse V. Karlsen

2
Punto a favor. Las palabras verdaderamente reservadas en Python se pueden encontrar aquí tinyurl.com/424663 Sin embargo, es una buena idea considerar las funciones y los tipos incorporados como reservados. list () en realidad genera una lista. Lo mismo ocurre con dict, range, etc.
gotgenes

Combinaría las dos últimas líneas, eliminando la variable temporal (a menos que necesite my_list más). Eso mejora la legibilidad.
Svante

Esta es más o menos la respuesta hacia la que estaba gravitando.
Robert Rossney

1
get_first_itemdebería aceptar un defaultparámetro (como dict.get) que por defecto sea Ninguno. Por lo tanto, la interfaz de la función comunica explícitamente lo que sucede si my_listestá vacío.
jfs

3
for item in get_list():
    return item

Esto es sucinto y hermoso.
gotgenes

Trágicamente, no funciona; lanza una excepción "TypeError: 'NoneType' no es iterable" si get_list () devuelve None.
Robert Rossney

Para corregir: "para el elemento en get_list () o []:"
itsadok

55
La pregunta supone claramente que get_list devuelve una lista. len y getitem son llamados por su resultado.
A. Coady

2

Hablando francamente, no creo que haya una mejor expresión idiomática: la tuya es clara y concisa, no hay necesidad de nada "mejor". Tal vez, pero esto es realmente una cuestión de gusto, usted podría cambiar if len(list) > 0:con if list:- una lista vacía siempre evaluará a falso.

En una nota relacionada, Python no es Perl (¡sin juego de palabras!), No tiene que obtener el código más genial posible.
En realidad, el peor código que he visto en Python, también fue genial :-) y completamente imposible de mantener.

Por cierto, la mayor parte de la solución que he visto aquí no tiene en cuenta cuando la lista [0] se evalúa como False (por ejemplo, cadena vacía o cero); en este caso, todos devuelven None y no el elemento correcto.


En Python, se considera un buen estilo escribir en if lst:lugar de hacerlo if len(lst) > 0:. Además, no use palabras clave de Python como nombres de variables; termina en lágrimas. Es común usar Lo lstcomo nombre de variable para alguna lista. Estoy seguro de que en este caso no quiso sugerir que se usara realmente listcomo un nombre de variable, solo quiso decir "alguna lista". Y, +1 para "cuando lst [0] se evalúa como False".
steveha

Sí, solo mantenía el mismo tipo de nombre de OP, para mayor claridad. Pero definitivamente tiene razón: siempre se debe tener cuidado de no anular las palabras clave de Python.
robar el

2

¿Idioma de Python para devolver el primer artículo o Ninguno?

El enfoque más pitónico es lo que demostró la respuesta más votada, y fue lo primero que se me ocurrió 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 desde una get_listfunción:

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

Otras formas demostradas para hacer esto aquí, con explicaciones

for

Cuando comencé a pensar en formas inteligentes de hacer esto, esta es la segunda cosa que pensé:

for item in get_list():
    return item

Esto supone que la función termina aquí, devolviendo implícitamente Nonesi get_listdevuelve 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 (corrigí el nombre de variable incorrecto) que también usa lo 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 de inmediato lo que está sucediendo. Pero si estamos escribiendo para facilitar la lectura y el mantenimiento, también deberíamos agregar el explícito return Noneal final:

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

cortar or [None]y seleccionar el índice cero

Esta también está en la respuesta más votada:

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

El segmento no es necesario y crea una lista adicional de un elemento en la memoria. Lo siguiente debería ser más eficiente. Para explicar, ordevuelve el segundo elemento si el primero está Falseen un contexto booleano, por lo que si get_listdevuelve 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 devuelve el segundo elemento si el primero está Trueen 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 línea):

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

next

Luego tenemos el siguiente uso inteligente del builtin nextyiter

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

Para explicar, iterdevuelve un iterador con un .nextmétodo. ( .__next__En Python 3.) A continuación, la orden interna nextllamadas que .nextel método, y si el iterador está agotado, devuelve el valor por defecto que damos, None.

expresión ternaria redundante ( a if b else c) y vuelta en círculo

Se propuso lo siguiente, pero sería preferible lo inverso, ya que la lógica generalmente se entiende mejor en lo positivo que en lo negativo. Como get_listse 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]

El mejor inverso:

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

Aún mejor, use una variable local para que get_listsolo se llame una vez, y primero tendrá la solución Pythonic recomendada:

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

2

En cuanto a los modismos, hay una receta itertools llamada nth.

De las recetas de itertools:

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

Si desea frases sencillas, considere instalar una biblioteca que implemente esta receta para usted, por ejemplo more_itertools:

import more_itertools as mit

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

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

Hay otra herramienta disponible que solo devuelve el primer elemento, llamado more_itertools.first.

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

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

Estas herramientas de iteración escalan genéricamente para cualquier iterable, no solo para listas.



1

Por curiosidad, corrí tiempos en dos de las soluciones. La solución que utiliza una declaración de devolución para finalizar prematuramente un bucle for es un poco más costosa en mi máquina con Python 2.5.1, sospecho que esto tiene que ver con la configuración del 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 horarios que obtuve:

listas_vacías:
  index_first_item:
    0.748353 segundos
    0,741086 segundos
    0,741191 segundos
    0,743543 segundos promedio
  return_first_item:
    0,785511 segundos
    0.822178 segundos
    0,782846 segundos
    0,796845 segundos promedio
listas_completas:
  index_first_item:
    0,762618 segundos
    0,788040 segundos
    0,786849 segundos
    0,779169 segundos promedio
  return_first_item:
    0,802735 segundos
    0.878706 segundos
    0,808781 segundos
    0,830074 segundos promedio
listas_mezcladas:
  index_first_item:
    0.791129 segundos
    0,743526 segundos
    0.744441 segundos
    0,759699 segundos promedio
  return_first_item:
    0.784801 segundos
    0,785146 segundos
    0.840193 segundos
    0,803380 segundos promedio

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

1
Las excepciones generalmente no se utilizan para el control de flujo.
Matt Green

No creo que alargar este código y agregarle un manejo de excepciones es exactamente el idioma que estaba buscando.
Robert Rossney

3
¡Pero este es un idioma común de Python! Por razones de rendimiento, es posible que no lo use; obtendrá un mejor rendimiento, if len(a) > 0:pero esto se considera un buen estilo. Tenga en cuenta lo bien que esto expresa el problema: "intente extraer el encabezado de una lista, y si eso no funciona, vuelva None". Busque en Google "EAFP" o busque aquí: python.net/~goodger/projects/pycon/2007/idiomatic/…
steveha

@steveha Depende de sus datos. Si len (a) suele ser> 0 y len (a) <1 es un estado poco frecuente, la captura de la excepción será realmente más eficaz.
limscoder

@limscoder, estoy de acuerdo contigo. Simplemente no entré en los detalles de cuándo podría no usarlo por razones de rendimiento; No dije que nunca usarías esto.
steveha

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

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

Por cierto: retrabajaría el flujo general de su 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))

(Evitar la repetición siempre que sea posible)


0

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 devolver una respuesta incorrecta en caso de una lista de números o cadenas según los comentarios a continuación.


¡Qué tal ([0,1] and 0) or None!
Kenly

Ese es un buen punto ... Inseguro de usar con colecciones de números en forma actual :(
VitalyB

Lo mismo con (['','A'] and '') or None.
Kenly

Si my_list[0]se evalúa como False, el resultado debería ser None.
Kenly

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

0

No estoy seguro de cuán pitónico es esto, pero hasta que haya una primera función en la biblioteca, incluyo esto en la fuente:

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

Es solo una línea (se ajusta al negro) y evita dependencias.



-1

Probablemente no sea la solución más rápida, pero nadie mencionó esta opción:

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

si get_list()puede regresar Nonepuede usar:

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

Ventajas:

-una línea

-solo llamas get_list()una vez

-fácil de comprender


-1

Mi caso de uso fue solo para establecer el valor de una variable local.

Personalmente encontré la prueba y excepto el limpiador de estilo para leer

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

que cortar una lista.

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

-1

Varias personas han sugerido hacer algo como esto:

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

Eso funciona en muchos casos, pero solo funcionará si la lista [0] no es igual a 0, Falso o una cadena vacía. Si list [0] es 0, False o una cadena vacía, el método devolverá None de forma incorrecta.

¡He creado este error en mi propio código demasiadas veces!


3
Esto debería ser un comentario debajo de las respuestas defectuosas en lugar de una respuesta independiente.
IJ Kennedy

-2

Puedes usar el método de extracción . En otras palabras, extraiga ese código en un método que luego llamaría.

No trataría de comprimirlo mucho más, los revestimientos parecen más difíciles de leer que la versión detallada. Y si usa el método de extracción, es un trazador de líneas;)



-3

no es la pitón idiomática equivalente a operadores ternarios de estilo C

cond and true_expr or false_expr

es decir.

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

Si un [0] es el número 0 o la cadena vacía (o cualquier otra cosa que se evalúe como falsa), esto devolverá Ninguno en lugar de lo que realmente es un [0].
Adam Rosenfield
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.