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

259

Je reçois new_tagun champ de texte de formulaire avec self.response.get("new_tag")et selected_tagsdes 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.striplistest une fonction qui supprime les espaces blancs à l'intérieur des chaînes de la liste.)

Mais dans le cas qui tag_listest vide (aucune nouvelle balise n'est entrée) mais il y en a selected_tags, new_tag_listcontient une chaîne vide " ".

Par exemple, à partir 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 contourner ce problème?

Zeynel
la source

Réponses:

718

1) Style presque anglais:

Testez la présence à l'aide de l' inopérateur, puis appliquez la removeméthode.

if thing in some_list: some_list.remove(thing)

La removeméthode supprimera uniquement la première occurrence de thing, afin de supprimer toutes les occurrences que vous pouvez utiliser à la whileplace 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 à une ligne)

2) Canard typé , EAFP style:

Cette attitude shoot-first-ask-questions-last est courante en Python. Au lieu de tester à l'avance si l'objet convient, il suffit d'effectuer l'opération et de saisir 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 sûr, la deuxième clause except dans l'exemple ci-dessus est non seulement d'humour discutable, mais totalement inutile (le but était d'illustrer le typage du canard pour les personnes qui ne connaissent pas le concept).

Si vous vous attendez à plusieurs occurrences de chose:

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

Cependant, avec le gestionnaire de contexte suppress () de contextlib (introduit en python 3.4), le code ci-dessus peut être simplifié comme suit :

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 a lambda, reduce(), filter()et map(), avec la permission d'un Lisp hacker qui les a manqués et soumis patches de travail *. Vous pouvez utiliser filterpour 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 pour votre cas: si vous souhaitez filtrer les éléments vides (en fait, les éléments où bool(item) == False, comme None, zéro, des chaînes vides ou d'autres collections vides), vous pouvez passer None comme premier argument:

cleaned_list = filter(None, some_list)
  • [mise à jour] : en Python 2.x, filter(function, iterable)utilisé pour être équivalent à [item for item in iterable if function(item)](ou [item for item in iterable if item]si le premier argument l'est None); en 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, maintenant il fonctionne comme une expression de générateur - c'est OK si vous parcourez seulement la liste nettoyée et la jetez, mais si vous avez vraiment besoin d'une liste, vous devez joindre l' filter()appel avec le list()constructeur.
  • * Ces constructions aromatisées Lispy sont considérées comme un peu étrangères en Python. Vers 2005, Guido parlait même de supprimerfilter - avec des compagnons mapet reduce(ils ne sont pas encore partis mais ont reduceété déplacés dans le module functools , qui vaut le coup d'œil si vous aimez les fonctions de haut niveau ).

4) Style mathématique:

Les compréhensions de liste sont devenues le style préféré pour la manipulation de liste en Python depuis son introduction dans la version 2.0 par PEP 202 . La raison est que la liste compréhensions fournissent une façon plus concise pour créer des listes dans des 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 où vous n'avez pas vraiment besoin (ou ne voulez pas) d'avoir une liste complète créée en mémoire - comme lorsque vous voulez simplement parcourir les éléments un par un. Si vous parcourez uniquement la liste, vous pouvez considérer une expression de générateur comme une compréhension de liste évaluée 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 génératrices ne sont pas toujours plus efficaces que les compréhensions de liste - veuillez profiler avant de vous plaindre
Paulo Scardine
la source
3
Puis-je suggérer d'omettre la gestion AttributeError dans (2)? C'est distrayant et non traité dans les autres sections (ou d'autres parties de la même section). Pire, quelqu'un pourrait copier ce code sans se rendre compte qu'il supprime de manière trop agressive les exceptions. La question d'origine suppose une liste, la réponse devrait aussi.
Jason R. Coombs
1
Réponse super complète! Super de l'avoir divisé en différentes sections par "Style". Merci!
halloleo
Quel est le plus rapide?
Sheshank S.12
12
try:
    s.remove("")
except ValueError:
    print "new_tag_list has no empty string"

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

Tim Pietzcker
la source
5

Si indexne trouve pas la chaîne recherchée, il lance le ValueErrorque vous voyez. Soit attraper ValueError:

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

ou use 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"
phihag
la source
Find () est-il un attribut de liste? Je reçois:>>> 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
L' remove()approche de Time Pietscker est beaucoup plus directe: elle montre directement ce que le code est censé faire (il n'y a en effet pas besoin d'index intermédiaire i).
Eric O Lebigot
1
@Zeynel non, il devrait être dans chaque Python, voir docs.python.org/library/string.html#string.find . Mais comme l'a souligné EOL, il est préférable d'utiliser simplement remove.
phihag
4

Ajouter cette réponse pour être complet, bien qu'elle ne soit utilisable que sous certaines conditions.

Si vous avez des listes très volumineuses, la suppression de la fin de la liste évite aux internes CPython de le faire memmove, dans les situations où vous pouvez réorganiser la liste. Il donne un gain de performances à supprimer de la fin de la liste, car il n'aura pas besoin de memmove chaque élément après celui que vous supprimez - retour d'une étape (1) .
Pour les suppressions ponctuelles, la différence de performances peut être acceptable, mais si vous avez une grande liste et devez supprimer de nombreux éléments - vous remarquerez probablement un impact sur les performances.

Bien que, il est vrai que dans ces cas, effectuer une recherche dans la liste complète est également susceptible d'être un goulot d'étranglement en matière de performances, sauf si les éléments sont principalement en tête de liste.

Cette méthode peut être utilisée pour une suppression 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 souhaiterez peut-être éviter de générer une erreur lorsque le itemn'est 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'aie testé cela avec CPython, il est fort probable que la plupart / toutes les autres implémentations Python utilisent un tableau pour stocker des listes en interne. Donc, à moins qu'ils n'utilisent une structure de données sophistiquée conçue pour un redimensionnement efficace des listes, ils ont probablement la même caractéristique de performance.

Un moyen simple de tester cela, comparez la différence de vitesse entre la suppression du début de la liste et la suppression du 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 de différence de vitesse où le deuxième exemple est plus rapide avec CPython et PyPy).

  1. Dans ce cas, vous pourriez envisager d'utiliser un set, surtout si la liste n'est pas destinée à stocker des doublons.
    En pratique, vous devrez peut-être stocker des données modifiables qui ne peuvent pas être ajoutées à a set. Vérifiez également sur btree si les données peuvent être commandées.
ideasman42
la source
3

Eek, ne fais rien de compliqué :)

Juste filter()vos tags. bool()renvoie Falsepour 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, mettez cette logique à l'intérieur striplist()afin qu'elle ne retourne pas de chaînes vides en premier lieu.

dfichter
la source
Merci! Toutes les bonnes réponses, mais je pense que je vais l'utiliser. C'est ma striplistfonction, comment puis-je intégrer votre solution: def striplist (l): "" "supprime les espaces blancs des chaînes dans une liste l" "" return ([x.strip () pour x in l])
Zeynel
1
@Zeynel: bien sûr. Vous pouvez soit mettre un test dans votre compréhension de la liste comme ceci: [x.strip() for x in l if x.strip()]ou utiliser intégré de Python mapet filterfonctionne comme ceci: filter(bool, map(str.strip, l)). Si vous voulez le tester, évaluer ce dans l'interpréteur interactif: filter(bool, map(str.strip, [' a', 'b ', ' c ', '', ' '])).
dfichter
Le filtre a un raccourci pour ce cas (évaluer l'élément dans un contexte booléen): utiliser à la Noneplace de boolpour le premier argument suffit.
Paulo Scardine
2

Voici une autre approche unilatérale à lancer:

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, n'effectue pas plusieurs passages dans la liste, ne nécessite pas de gestion d'exception supplémentaire et renvoie l'objet correspondant ou Aucun 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 à une ligne qui ne lève pas d'exceptions, next () est la voie à suivre, car c'est l'une des rares fonctions Python qui prend en charge un argument par défaut.

Dane White
la source
1

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, donc j'ai trouvé ceci:

list = ["a", "b", "c"]
if "a" in str(list):
    list.remove("a")
SollyBunny
la source
3
Vous ne devez pas écraser la liste intégrée. Et la conversion en chaîne n'est pas nécessaire dans le 2e extrait.
Robert Caspary