web-dev-qa-db-fra.com

Comment supprimer un élément dans une liste s'il existe?

Je reçois new_tag d'un champ de texte de formulaire avec self.response.get("new_tag") et selected_tags de champs de case à cocher avec

self.response.get_all("selected_tags")

Je les combine comme ceci:

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

(f1.striplist est une fonction qui supprime les espaces dans les chaînes de la liste.)

Mais dans le cas où tag_list est vide (aucune nouvelle balise n'est entrée) mais il y a quelques selected_tags, new_tag_list contient une chaîne vide " ".

Par exemple, de logging.info:

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

Comment puis-je me débarrasser de la chaîne vide?

S'il y a une chaîne vide dans la liste:

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

Mais s'il n'y a pas de chaîne vide:

>>> 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"

Mais cela donne:

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

Pourquoi cela se produit-il et comment puis-je y remédier?

211
Zeynel

1) style presque anglais:

Testez la présence à l'aide de l'opérateur in, puis appliquez la méthode remove.

if thing in some_list: some_list.remove(thing)

La méthode remove ne supprime que la première occurrence de thing. Pour supprimer toutes les occurrences, vous pouvez utiliser while au lieu de if.

while thing in some_list: some_list.remove(thing)    
  • Assez simple, probablement mon choix.pour les petites listes (ne peut pas résister à des one-liners)

2) type canard , EAFP style:

Cette attitude de tir-première-demande-dernière-question est courante en Python. Au lieu de tester à l'avance si l'objet convient, il suffit de procéder à l'opération et de détecter les exceptions pertinentes:

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!")

Bien entendu, la deuxième clause d’exception de l’exemple ci-dessus n’est pas seulement d’humour discutable, elle est tout à fait inutile (il s’agissait d’illustrer le typage de canards pour des personnes qui ne connaissaient pas le concept).

Si vous vous attendez à plusieurs occurrences de chose:

while True:
    try:
        some_list.remove(thing)
    except ValueError:
        break
  • un peu verbeux pour ce cas d'utilisation spécifique, mais très idiomatique en Python.
  • cela fonctionne mieux que n ° 1
  • PEP 46 a proposé une syntaxe plus courte pour essayer/sauf un usage simple qui serait pratique ici, mais elle n’a pas été approuvée.

Cependant, avec contextmanager de suppress () de contextlib (introduit dans python 3.4), le code ci-dessus peut être simplifié:

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

Encore une fois, si vous vous attendez à plusieurs occurrences de chose:

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

3) style fonctionnel:

Vers 1993, Python reçut lambda, reduce(), filter() et map(), gracieuseté d'un pirate LISP les a ratées et a soumis des correctifs de travail *. Vous pouvez utiliser filter pour supprimer des éléments de la liste:

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

Il existe un raccourci qui peut être utile dans votre cas: si vous souhaitez filtrer les éléments vides (en fait, les éléments où bool(item) == False, comme None, zéro, les chaînes vides ou d'autres collections vides), vous pouvez passer None comme premier argument:

cleaned_list = filter(None, some_list)
  • [update] : dans Python 2.x, filter(function, iterable) était équivalent à [item for item in iterable if function(item)] ( ou [item for item in iterable if item] si le premier argument est None); dans Python 3.x, il est maintenant équivalent à (item for item in iterable if function(item)). La différence subtile est que le filtre utilisé pour renvoyer une liste, fonctionne maintenant comme une expression de générateur - cela n’est pas grave si vous ne parcourez que la liste nettoyée et que vous la supprimez, mais si vous avez vraiment besoin d’une liste, vous devez inclure le filter() appel avec le constructeur list().
  • * Ces constructions à saveur de Lispy sont considérées comme un peu étrangères à Python. Vers 2005, Guido parlait même de laisser tomber filter - avec ses compagnons map et reduce (ils ne sont pas encore partis mais reduce a été déplacé. dans le module functools , qui vaut le détour si vous voulez fonctions de poids fort ).

4) style mathématique:

Compréhensions de liste est devenu le style préféré pour la manipulation de liste dans Python depuis son introduction dans la version 2.0 par PEP 202 . Cela s'explique par le fait que la compréhension de liste fournit un moyen plus concis de créer des listes dans les situations où map() et filter() et/ou des boucles imbriquées seraient actuellement utilisées.

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

Les expressions de générateur ont été introduites dans la version 2.4 par PEP 289 . Une expression de générateur est préférable pour les situations dans lesquelles vous n'avez pas vraiment besoin (ou souhaitez) d'avoir une liste complète créée en mémoire, comme lorsque vous souhaitez simplement parcourir les éléments un à un. Si vous ne parcourez que la liste, vous pouvez considérer une expression génératrice comme une compréhension liste paresseuse :

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

Remarques

  1. vous voudrez peut-être utiliser l'opérateur d'inégalité != au lieu de is not ( la différence est importante )
  2. pour les critiques des méthodes impliquant une copie de liste: contrairement à la croyance populaire, les expressions de générateur ne sont pas toujours plus efficaces que la compréhension de liste - veuillez profiler avant de vous plaindre
619
Paulo Scardine
try:
    s.remove("")
except ValueError:
    print "new_tag_list has no empty string"

Notez que cela ne supprimera qu'une instance de la chaîne vide de votre liste (comme votre code l'aurait aussi). Votre liste peut-elle en contenir plusieurs?

12
Tim Pietzcker

Si index ne trouve pas la chaîne recherchée, il jette le ValueError que vous voyez. Soit attraper le ValueError:

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

o utilise find, qui renvoie -1 dans ce cas.

i = s.find("")
if i >= 0:
    del s[i]
else:
    print "new_tag_list has no empty string"
5
phihag

Ajouter cette réponse pour compléter, bien que ce ne soit utilisable que sous certaines conditions.

Si vous avez des listes très volumineuses, supprimer de la fin de la liste évite aux éléments internes de CPython de devoir memmove, dans les cas où vous pouvez réorganiser la liste. Cela donne un gain de performance à supprimer de la fin de la liste, car il n’aura pas besoin de memmoveevery item après celui que vous supprimez - de retour en arrière (1).
Pour les retraits ponctuels, la différence de performances peut être acceptable, mais si vous avez une longue liste et devez supprimer de nombreux éléments, vous remarquerez probablement un impact négatif sur les performances.

Certes, dans ces cas, une recherche dans une liste complète risque également de gêner les performances, à moins que les éléments figurent généralement en tête de liste.

Cette méthode peut être utilisée pour une élimination plus efficace,
tant que la réorganisation de la liste est acceptable. (2)

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

Vous voudrez peut-être éviter de générer une erreur lorsque le item ne figure pas dans la liste.

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. Bien que j'ai testé cela avec CPython, il est fort probable que la plupart/toutes les autres implémentations Python utilisent un tableau pour stocker les listes en interne. Par conséquent, à moins d'utiliser une structure de données sophistiquée conçue pour un redimensionnement efficace des listes, ils ont probablement les mêmes caractéristiques de performance.

Un moyen simple de tester cela consiste à comparer la différence de vitesse entre supprimer du début de la liste et supprimer le dernier élément:

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

Avec:

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

(donne un ordre de grandeur différence de vitesse où le deuxième exemple est plus rapide avec CPython et PyPy).

  1. Dans ce cas, vous pouvez envisager d'utiliser un set, en particulier si la liste n'est pas destinée à stocker des doublons.
    En pratique, vous aurez peut-être besoin de stocker des données mutables qui ne peuvent pas être ajoutées à un set. Vérifiez également si les données peuvent être commandées.
4
ideasman42

Eek, ne fais rien d'aussi compliqué:)

Juste filter() vos balises. bool() renvoie False pour les chaînes vides, donc au lieu de

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

tu devrais écrire

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

ou mieux encore, placez cette logique dans striplist() afin qu'elle ne renvoie pas de chaîne vide.

3
dfichter

Voici une autre approche à ne pas manquer:

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

Il ne crée pas de copie de liste, ne passe pas plusieurs fois dans la liste, ne nécessite pas de gestion des exceptions supplémentaire et renvoie l'objet correspondant ou None s'il n'y a pas de correspondance. Le seul problème est que cela fait une longue déclaration.

En général, lorsque vous recherchez une solution one-liner qui ne génère pas d'exceptions, next () est la meilleure solution, car c'est l'une des rares fonctions Python qui prend en charge un argument par défaut.

2
Dane White

Tout ce que vous avez à faire est ceci

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

mais cette méthode a un problème. Vous devez mettre quelque chose à la place sauf si j'ai trouvé ceci:

list = ["a", "b", "c"]
if "a" in str(list):
    list.remove("a")
0
SollyBunny