¿Cómo establecer la variable local en la lista de comprensión?

7 minutos de lectura

Avatar de usuario de Hao Tan
tan hao

Tengo un método que toma una lista y devuelve un objeto:

# input a list, returns an object
def map_to_obj(lst):
    a_list = f(lst)
    return a_list[0] if a_list else None

Quiero obtener una lista que contenga todos los elementos mapeados que no están None.

Como esto:

v_list = [v1, v2, v3, v4]

[map_to_obj(v) for v in v_list if map_to_obj(v)]

Pero no me parece bien llamar a la map_to_obj método dos veces en la lista de comprensión.

¿Hay alguna manera de tener variables locales en las listas de comprensión para que pueda tener un mejor rendimiento?

¿O el compilador lo optimiza automáticamente?

Esto es lo que quiero:

(sml like)
[let mapped = map_to_obj(v) in for v in v_list if mapped end] 

Avatar de usuario de Lying Dog
perro mentiroso

Utilice la comprensión de listas anidadas:

[x for x in [map_to_obj(v) for v in v_list] if x]

o mejor aún, una lista de comprensión en torno a una expresión generadora:

[x for x in (map_to_obj(v) for v in v_list) if x]

  • Es una buena respuesta y, por supuesto, es la misma que la de behzad, con listas de comprensión en lugar de map y filter… Voy a votar porque me gustó cómo se tradujo Lying Dog filter en términos de lc, pero el OP puede aprobar cualquiera de esas respuestas, ya que ambas son buenas y útiles.

    – gboffi

    31/10/2014 a las 10:57


  • La comprensión interior debe ser una expresión generadora. No es necesario crear la lista completa y solo luego desechar los elementos vacíos para crear otra lista.

    –Tim Pietzcker

    31 de octubre de 2014 a las 11:04

  • El consejo de @Lying Imho Tim es sólido y significativo, considere editar su respuesta para reflejar su sugerencia.

    – gboffi

    31 de octubre de 2014 a las 11:12

  • @cowbert: Si un lambda (en realidad, cualquier función implementada de Python que la comprensión pueda en línea para evitar la sobrecarga de la llamada) está involucrada, sí, el listcomp gana. map/filter Sin embargo, pueden ganar si la función de devolución de llamada es una C incorporada, ya que pueden enviar todo el trabajo a la capa C, sin pasar por la sobrecarga del intérprete de código de bytes, que los listcomps no pueden evitar, ya que son funciones de nivel de Python, solo con soporte directo de código de bytes para el paso de añadir a la lista.

    – ShadowRanger

    27 de octubre de 2018 a las 2:29


  • Ejemplo tonto: cambiar la respuesta de behzad para reemplazar lambda t: t is not None con None.__ne__aunque es un poco raro en su funcionamiento (devuelve False por otro Nonearena NotImplementedque resulta cierto, por todo lo demás), mejoraría la velocidad de esa solución (en pruebas locales, la filter el trabajo se reduce en casi un factor de 2), aunque a expensas de la comprensibilidad.

    – ShadowRanger

    27 de octubre de 2018 a las 2:33


Avatar de usuario de Xavier Guihot
Xavier Guihot

A partir de Python 3.8y la introducción de expresiones de asignación (PEP 572) (:= operador), es posible usar una variable local dentro de una lista de comprensión para evitar llamar a la misma función dos veces.

En nuestro caso, podemos nombrar la evaluación de map_to_obj(v) como variable o mientras usa el resultado de la expresión para filtrar la lista; y así usar o como el valor mapeado:

[o for v in [v1, v2, v3, v4] if (o := map_to_obj(v))]

  • Esto también tiene la ventaja de no recorrer la lista dos veces.

    – Alex Reining

    23 de enero a las 4:14

  • @Alex ¿Quieres decir en contraste con la respuesta de Lying Dog? Porque en comparación con otras respuestas como las de Vincent y Bruno, no atraviesan la lista dos veces.

    – wjandrea

    4 sep a las 20:45

  • @wjandrea: sí, quise decir en contraste con la respuesta (todavía) más votada. Escribí mi comentario en enero, antes de que trending-sort promoviera correctamente esta respuesta al primer lugar.

    – Alex Reining

    4 sep a las 21:05

Una asignación de variable es solo un enlace singular:

[x   for v in l   for x in [v]]

Esta es una respuesta más general y también más cercana a lo que propusiste. Así que para tu problema puedes escribir:

[x   for v in v_list   for x in [map_to_obj(v)]   if x]

  • Esto difiere de mi respuesta solo por el uso de una lista de 1 elemento en lugar de una tupla de 1 elemento, y creo que la tupla supera a la lista cuando no necesita ser mutable. 🙂

    – Ovaflo

    12 de noviembre de 2018 a las 0:13

  • @Ovaflo IIRC, en CPython hay un caso especial para que la lista literal aquí se compile como una tupla. Tiene una longitud fija y no se puede mutar, por lo que tiene sentido. Pero si haces algo como [map_to_obj(v)] * nque por supuesto crea una lista.

    – wjandrea

    4 sep a las 20:58

avatar de usuario de behzad.nouri
behzad.nouri

Puede evitar el recálculo utilizando python incorporado filter:

list(filter(lambda t: t is not None, map(map_to_obj, v_list)))

Una variable local se puede establecer dentro de una comprensión haciendo un poco de trampa y usando un ‘para’ adicional que “itera” a través de una tupla de 1 elemento que contiene el valor deseado para la variable local. Aquí hay una solución al problema del OP usando este enfoque:

[o for v in v_list for o in (map_to_obj(v),) if o]

Aquí, o es la variable local siendo igual a map_to_obj(v) para cada v.

En mis pruebas, esto es un poco más rápido que la expresión del generador anidado de Lying Dog (y también más rápido que la doble llamada del OP a map_to_obj(v)que, sorprendentemente, puede ser más rápido que la expresión del generador anidado si el map_to_obj la función no es demasiado lenta).

Las comprensiones de lista están bien para los casos simples, pero a veces un simple viejo for loop es la solución más simple:

other_list = []
for v in v_list:
    obj = map_to_obj(v)
    if obj:
        other_list.append(obj)

Ahora, si realmente desea una compilación de lista y no desea crear una lista tmp, puede usar las versiones iteradoras de filter y map:

import itertools as it
result = list(it.ifilter(None, it.imap(map_to_obj, v_list)))

o más simplemente:

import itertools as it
result = filter(None, it.imap(map_to_obj, v_list)))

Las versiones del iterador no crean una lista temporal, usan una evaluación diferida.

Avatar de usuario de Hao Tan
tan hao

He descubierto una forma de usar reduce:

def map_and_append(lst, v):
    mapped = map_to_obj(v)
    if mapped is not None:
        lst.append(mapped)
    return lst

reduce(map_and_append, v_list, [])

¿Qué tal el rendimiento de esto?

  • Puedes usar el timeit módulo para cronometrar las diferentes soluciones, pero su fragmento anterior es una forma arbitrariamente demasiado compleja de hacer algo muy simple, y dudo que sea más rápido o más eficiente en el espacio que el antiguo bucle for o las soluciones de filtro/imap. .

    – bruno destuilliers

    31 de octubre de 2014 a las 11:11

  • @Bruno ¡Me gusta tu «demasiado complejo»!

    – gboffi

    31 de octubre de 2014 a las 11:16

  • Si miro las respuestas sin itertools, a pesar del rendimiento, me atrevo a decir que la de Lying Dog es la más expresiva de la intención y, por lo tanto, la más legible. ¿Y qué hay del rendimiento? pues no se cuanto cpu gastas en tu f(lst) llamadas pero quitando el Nones de una forma u otra es poco probable que cambie el panorama completo.

    – gboffi

    31 de octubre de 2014 a las 11:18


  • reduce realmente no se requiere aquí. Su uso es para reducir una lista de valores a un solo valor (típicamente, una lista de escalares a un solo escalar), no para transformar una lista en otra lista.

    – Chepner

    31/10/2014 a las 12:55

  • Cuidado, esto tiene el efecto secundario de modificar lst, que consideraría un error, aunque solo surgiría si tuviera otra referencia. Podrías arreglarlo evitando .append() y en cambio haciendo return lst+[mapped] if mapped is not None else lst.

    – wjandrea

    4 sep a las 21:23


¿Ha sido útil esta solución?