¿Cómo eliminar un elemento de una lista si existe?

259

Estoy obteniendo new_tagde un campo de texto de formulario con self.response.get("new_tag")y selected_tagsde campos de casilla de verificación con

self.response.get_all("selected_tags")

Los combino así:

tag_string = new_tag
new_tag_list = f1.striplist(tag_string.split(",") + selected_tags)

( f1.striplistes una función que elimina espacios en blanco dentro de las cadenas de la lista).

Pero en el caso de que tag_listesté vacío (no se ingresan nuevas etiquetas) pero hay algunas selected_tags, new_tag_listcontiene una cadena vacía " ".

Por ejemplo, de logging.info:

new_tag
selected_tags[u'Hello', u'Cool', u'Glam']
new_tag_list[u'', u'Hello', u'Cool', u'Glam']

¿Cómo me deshago de la cadena vacía?

Si hay una cadena vacía en la lista:

>>> s = [u'', u'Hello', u'Cool', u'Glam']
>>> i = s.index("")
>>> del s[i]
>>> s
[u'Hello', u'Cool', u'Glam']

Pero si no hay una cadena vacía:

>>> s = [u'Hello', u'Cool', u'Glam']
>>> if s.index(""):
        i = s.index("")
        del s[i]
    else:
        print "new_tag_list has no empty string"

Pero esto da:

Traceback (most recent call last):
  File "<pyshell#30>", line 1, in <module>
    if new_tag_list.index(""):
        ValueError: list.index(x): x not in list

¿Por qué sucede esto y cómo puedo solucionarlo?

Zeynel
fuente

Respuestas:

718

1) Estilo casi inglés:

Pruebe la presencia utilizando el inoperador, luego aplique el removemétodo.

if thing in some_list: some_list.remove(thing)

El removemétodo eliminará solo la primera aparición de thing, para eliminar todas las ocurrencias que puede usar en whilelugar de if.

while thing in some_list: some_list.remove(thing)    
  • Bastante simple, probablemente sea mi elección. Para listas pequeñas (no puedo resistir una sola línea)

2) Pato-mecanografiado , EAFP estilo:

Esta actitud de disparar primero-preguntar-hacer-última es común en Python. En lugar de probar por adelantado si el objeto es adecuado, simplemente realice la operación y capture las excepciones relevantes:

try:
    some_list.remove(thing)
except ValueError:
    pass # or scream: thing not in some_list!
except AttributeError:
    call_security("some_list not quacking like a list!")

Por supuesto, la segunda cláusula, excepto en el ejemplo anterior, no es solo de humor cuestionable sino totalmente innecesaria (el punto era ilustrar la escritura de pato para personas que no están familiarizadas con el concepto).

Si espera múltiples ocurrencias de cosas:

while True:
    try:
        some_list.remove(thing)
    except ValueError:
        break
  • un poco detallado para este caso de uso específico, pero muy idiomático en Python.
  • esto funciona mejor que el n. ° 1
  • PEP 463 propuso una sintaxis más corta para probar / excepto el uso simple que sería útil aquí, pero no fue aprobada.

Sin embargo, con contextmanager suppress () contextmanager (introducido en python 3.4) el código anterior se puede simplificar a esto:

with suppress(ValueError, AttributeError):
    some_list.remove(thing)

Nuevamente, si espera múltiples ocurrencias de cosas:

with suppress(ValueError):
    while True:
        some_list.remove(thing)

3) estilo funcional:

Alrededor de 1993, consiguió Python lambda, reduce(), filter()y map(), gracias a la Lisp hacker que los echaba de menos y presentados parches de trabajo *. Puede usar filterpara eliminar elementos de la lista:

is_not_thing = lambda x: x is not thing
cleaned_list = filter(is_not_thing, some_list)

Hay un acceso directo que puede ser útil para su caso: si desea filtrar elementos vacíos (de hecho, elementos bool(item) == Falsecomo Nonecero, cadenas vacías u otras colecciones vacías), puede pasar Ninguno como primer argumento:

cleaned_list = filter(None, some_list)
  • [actualización] : en Python 2.x, filter(function, iterable)solía ser equivalente a [item for item in iterable if function(item)](o [item for item in iterable if item]si el primer argumento es None); en Python 3.x, ahora es equivalente a (item for item in iterable if function(item)). La sutil diferencia es que el filtro utilizado para devolver una lista, ahora funciona como una expresión generadora: esto está bien si solo está iterando sobre la lista limpia y descartándola, pero si realmente necesita una lista, debe encerrar la filter()llamada con el list()constructor
  • * Estas construcciones con sabor a Lispy se consideran un poco extraterrestres en Python. Alrededor de 2005, Guido incluso estaba hablando de dejar caerfilter , junto con compañeros mapy reduce(aún no se han ido, pero reducese trasladó al módulo functools , que vale la pena ver si le gustan las funciones de alto orden ).

4) Estilo matemático:

La comprensión de listas se convirtió en el estilo preferido para la manipulación de listas en Python desde que se introdujo en la versión 2.0 de PEP 202 . La razón detrás de esto es que las listas por comprensión proporcionan una forma más concisa para crear listas en situaciones en las que map()y filter()serían utilizadas actualmente y / o bucles anidados.

cleaned_list = [ x for x in some_list if x is not thing ]

Las expresiones generadoras fueron introducidas en la versión 2.4 por PEP 289 . Una expresión generadora es mejor para situaciones en las que realmente no necesita (o desea) tener una lista completa creada en la memoria, como cuando solo desea iterar sobre los elementos uno por uno. Si solo está iterando sobre la lista, puede pensar en una expresión generadora como una comprensión perezosa de la lista evaluada :

for item in (x for x in some_list if x is not thing):
    do_your_thing_with(item)

Notas

  1. es posible que desee utilizar el operador de desigualdad en !=lugar de is not( la diferencia es importante )
  2. Para los críticos de los métodos que implican una copia de la lista: contrariamente a la creencia popular, las expresiones generadoras no siempre son más eficientes que las comprensiones de listas.
Paulo Scardine
fuente
3
¿Puedo sugerir omitir el manejo de AttributeError en (2)? Es una distracción y no se maneja en las otras secciones (u otras partes de la misma sección). Peor aún, alguien podría copiar ese código sin darse cuenta de que está suprimiendo agresivamente las excepciones. La pregunta original supone una lista, la respuesta también debería.
Jason R. Coombs
1
Respuesta super comprensiva! Genial tenerlo dividido en diferentes secciones por "Estilo". ¡Gracias!
Halloleo
¿Cuál es el más rápido?
Sheshank S.
12
try:
    s.remove("")
except ValueError:
    print "new_tag_list has no empty string"

Tenga en cuenta que esto solo eliminará una instancia de la cadena vacía de su lista (como también lo habría hecho su código). ¿Puede su lista contener más de uno?

Tim Pietzcker
fuente
5

Si indexno encuentra la cadena buscada, arroja la ValueErrorque está viendo. O coger el ValueError:

try:
    i = s.index("")
    del s[i]
except ValueError:
    print "new_tag_list has no empty string"

o use find, que devuelve -1 en ese caso.

i = s.find("")
if i >= 0:
    del s[i]
else:
    print "new_tag_list has no empty string"
phihag
fuente
¿Find () es un atributo de lista? Estoy obteniendo:>>> s [u'Hello', u'Cool', u'Glam'] >>> i = s.find("") Traceback (most recent call last): File "<pyshell#42>", line 1, in <module> i = s.find("") AttributeError: 'list' object has no attribute 'find'
Zeynel
2
El remove()enfoque de Time Pietscker es mucho más directo: muestra directamente lo que debe hacer el código (de hecho, no hay necesidad de un índice intermedio i).
Eric O Lebigot
1
@Zeynel no, debería estar en cada Python, consulte docs.python.org/library/string.html#string.find . Pero como EOL señaló, simplemente usar remove es muchísimo mejor.
phihag
4

Agregando esta respuesta para completar, aunque solo se puede usar bajo ciertas condiciones.

Si tiene listas muy grandes, eliminar del final de la lista evita que los componentes internos de CPython tengan que hacerlo memmove, para situaciones en las que puede reordenar la lista. Proporciona una ganancia de rendimiento para eliminar del final de la lista, ya que no necesitará memmove cada elemento después del que está eliminando: retroceda un paso (1) .
Para las eliminaciones únicas, la diferencia de rendimiento puede ser aceptable, pero si tiene una lista grande y necesita eliminar muchos elementos, probablemente notará un impacto en el rendimiento.

Aunque es cierto que, en estos casos, hacer una búsqueda en la lista completa también puede ser un cuello de botella en el rendimiento, a menos que los elementos estén principalmente al principio de la lista.

Este método puede usarse para una eliminación más eficiente,
siempre que sea aceptable reordenar la lista. (2)

def remove_unordered(ls, item):
    i = ls.index(item)
    ls[-1], ls[i] = ls[i], ls[-1]
    ls.pop()

Es posible que desee evitar generar un error cuando el itemno está en la lista.

def remove_unordered_test(ls, item):
    try:
        i = ls.index(item)
    except ValueError:
        return False
    ls[-1], ls[i] = ls[i], ls[-1]
    ls.pop()
    return True

  1. Si bien probé esto con CPython, es probable que la mayoría / todas las demás implementaciones de Python usen una matriz para almacenar listas internamente. Por lo tanto, a menos que utilicen una estructura de datos sofisticada diseñada para cambiar el tamaño de la lista de manera eficiente, es probable que tengan la misma característica de rendimiento.

Una manera simple de probar esto, compara la diferencia de velocidad de eliminar del frente de la lista con eliminar el último elemento:

python -m timeit 'a = [0] * 100000' 'while a: a.remove(0)'

Con:

python -m timeit 'a = [0] * 100000' 'while a: a.pop()'

(da un orden de diferencia de velocidad de magnitud donde el segundo ejemplo es más rápido con CPython y PyPy).

  1. En este caso, puede considerar usar un set, especialmente si la lista no está destinada a almacenar duplicados.
    En la práctica, es posible que necesite almacenar datos mutables que no se pueden agregar a a set. También verifique en btree si los datos se pueden ordenar.
ideasman42
fuente
3

Eek, no hagas nada tan complicado :)

Solo filter()tus etiquetas. bool()devuelve Falsecadenas vacías, por lo que en lugar de

new_tag_list = f1.striplist(tag_string.split(",") + selected_tags)

Deberías escribir

new_tag_list = filter(bool, f1.striplist(tag_string.split(",") + selected_tags))

o mejor aún, coloque esta lógica dentro striplist()para que no devuelva cadenas vacías en primer lugar.

dfichter
fuente
¡Gracias! Todas son buenas respuestas, pero creo que usaré esto. Esta es mi striplistfunción, ¿cómo incorporo su solución: def striplist (l): "" "elimina espacios en blanco de las cadenas en una lista l" "" return ([x.strip () para x en l])
Zeynel
1
@ Zeynel: claro. Usted podría poner una prueba de comprensión dentro de su lista de la siguiente manera: [x.strip() for x in l if x.strip()]o el uso de Python incorporada mapy filterfunciones de la siguiente manera: filter(bool, map(str.strip, l)). Si quieres probarlo, evaluar esto en el intérprete interactivo: filter(bool, map(str.strip, [' a', 'b ', ' c ', '', ' '])).
dfichter
Filter tiene un atajo para este caso (evaluar el elemento en contexto booleano): usar en Nonelugar de boolpara el primer argumento es suficiente.
Paulo Scardine
2

Aquí hay otro enfoque de una sola línea para tirar:

next((some_list.pop(i) for i, l in enumerate(some_list) if l == thing), None)

No crea una copia de la lista, no realiza múltiples pases a través de la lista, no requiere un manejo de excepciones adicional y devuelve el objeto coincidente o Ninguno si no hay una coincidencia. El único problema es que hace una declaración larga.

En general, cuando se busca una solución de una línea que no arroje excepciones, next () es el camino a seguir, ya que es una de las pocas funciones de Python que admite un argumento predeterminado.

Dane White
fuente
1

Todo lo que tienes que hacer es esto

list = ["a", "b", "c"]
    try:
        list.remove("a")
    except:
        print("meow")

Pero ese método tiene un problema. Tienes que poner algo en el lugar de excepción, así que encontré esto:

list = ["a", "b", "c"]
if "a" in str(list):
    list.remove("a")
SollyBunny
fuente
3
No debe sobrescribir la lista incorporada. Y la conversión a una cadena no es necesaria en el segundo fragmento.
Robert Caspary