Comment modifier les entrées de la liste pendant la boucle for?

178

Maintenant, je sais qu'il n'est pas sûr de modifier la liste pendant une boucle itérative. Cependant, supposons que je dispose d'une liste de chaînes et que je veuille supprimer les chaînes elles-mêmes. Le remplacement des valeurs mutables compte-t-il comme une modification?

Alex
la source
20
Une chaîne ne crée pas de valeur mutable.
user470379
4
@ user470379: Le fait de savoir si les éléments de la liste sont modifiables n'est pas pertinent pour savoir s'il est sûr ou non de modifier la liste dans laquelle ils se trouvent pendant la boucle.
martineau

Réponses:

144

C'est considéré comme une mauvaise forme. Utilisez plutôt une compréhension de liste, avec une attribution de tranche si vous devez conserver les références existantes à la liste.

a = [1, 3, 5]
b = a
a[:] = [x + 2 for x in a]
print(b)
Ignacio Vazquez-Abrams
la source
10
L'affectation des tranches est astucieuse et évite de modifier l'original pendant la boucle, mais nécessite la création d'une liste temporaire de la longueur de l'original.
martineau
11
pourquoi attribuons-nous b = a?
Vigrond
9
@Vigrond: Ainsi, lorsque l' print binstruction est exécutée, vous pouvez dire si elle a aété modifiée sur place plutôt que remplacée. Une autre possibilité aurait été print b is ade voir si les deux font toujours référence au même objet.
martineau
12
pourquoi un [:] = et pas seulement un =?
kdubs
10
@kdubs: "... avec attribution de tranche si vous devez conserver les références existantes à la liste."
Ignacio Vazquez-Abrams
163

Étant donné que la boucle ci-dessous ne modifie que les éléments déjà vus, elle serait considérée comme acceptable:

a = ['a',' b', 'c ', ' d ']

for i, s in enumerate(a):
    a[i] = s.strip()

print(a) # -> ['a', 'b', 'c', 'd']

Ce qui est différent de:

a[:] = [s.strip() for s in a]

en ce qu 'il ne nécessite pas la création d'une liste temporaire et une affectation de celle-ci pour remplacer l'original, bien qu'il nécessite plus d'opérations d'indexation.

Attention: bien que vous puissiez modifier les entrées de cette manière, vous ne pouvez pas modifier le nombre d'éléments dans le listsans risquer de rencontrer des problèmes.

Voici un exemple de ce que je veux dire: la suppression d'une entrée perturbe l'indexation à partir de ce point:

b = ['a', ' b', 'c ', ' d ']

for i, s in enumerate(b):
    if s.strip() != b[i]:  # leading or trailing whitespace?
        del b[i]

print(b)  # -> ['a', 'c ']  # WRONG!

(Le résultat est faux car il n'a pas supprimé tous les éléments qu'il aurait dû.)

Mettre à jour

Comme il s'agit d'une réponse assez populaire, voici comment supprimer efficacement les entrées «sur place» (même si ce n'est pas exactement la question):

b = ['a',' b', 'c ', ' d ']

b[:] = [entry for entry in b if entry.strip() == entry]

print(b)  # -> ['a']  # CORRECT
Martineau
la source
3
Pourquoi Python ne fait-il qu'une copie de l'élément individuel dans la syntaxe for i in a? Ceci est très contre-intuitif, apparemment différent des autres langages et a entraîné des erreurs dans mon code que j'ai dû déboguer pendant une longue période. Le didacticiel Python ne le mentionne même pas. Mais il doit y avoir une raison à cela?
xji
1
@JIXiang: Il ne fait pas de copies. Il attribue simplement le nom de la variable de boucle aux éléments successifs ou à la valeur de l'objet itéré.
martineau
1
Eww, pourquoi utiliser deux noms ( a[i]et s) pour le même objet dans la même ligne alors que vous n'êtes pas obligé? Je préfère de loin le faire a[i] = a[i].strip().
Navin le
3
@Navin: Parce a[i] = s.strip()qu'une seule opération d'indexation.
martineau
1
@martineau enumerate(b)effectue une opération d'indexation à chaque itération et vous en faites une autre avec a[i] =. AFAIK il est impossible d'implémenter cette boucle en Python avec seulement 1 opération d'indexation par itération de boucle :(
Navin
18

Une variante de boucle de plus, me semble plus propre qu'une avec enumerate ():

for idx in range(len(list)):
    list[idx]=... # set a new value
    # some other code which doesn't let you use a list comprehension
Eugène Shatsky
la source
19
Beaucoup envisagent d'utiliser quelque chose comme range(len(list))en Python une odeur de code.
martineau
2
@Reishin: Puisqu'il enumerates'agit d'un générateur, il ne crée pas une liste de tuples, il les crée un par un au fur et à mesure qu'il parcourt la liste. La seule façon de savoir ce qui est le plus lent serait de le faire timeit.
martineau
3
Le code @martineau pourrait ne pas être bien joli, mais selon il timeit enumerateest plus lent
Reishin
2
@Reishin: Votre code d'analyse comparative n'est pas complètement valide car il ne prend pas en compte la nécessité de récupérer la valeur de la liste à l'index donné - ce qui n'est pas non plus indiqué dans cette réponse.
martineau
4
@Reishin: Votre comparaison est invalide précisément pour cette raison. Il mesure la surcharge en boucle de manière isolée. Pour être concluant, le temps qu'il faut à la boucle entière pour s'exécuter doit être mesuré en raison de la possibilité que les différences de frais généraux puissent être atténuées par les avantages fournis au code dans la boucle de bouclage d'une certaine manière - sinon vous ne comparez pas les pommes à pommes.
martineau
11

La modification de chaque élément lors de l'itération d'une liste est très bien, tant que vous ne modifiez pas ajouter / supprimer des éléments à la liste.

Vous pouvez utiliser la compréhension de liste:

l = ['a', ' list', 'of ', ' string ']
l = [item.strip() for item in l]

ou faites simplement la C-styleboucle for:

for index, item in enumerate(l):
    l[index] = item.strip()
cizix
la source
4

Non, vous ne modifieriez pas le "contenu" de la liste, si vous pouviez muter les chaînes de cette façon. Mais en Python, ils ne sont pas modifiables. Toute opération de chaîne renvoie une nouvelle chaîne.

Si vous aviez une liste d'objets que vous saviez modifiables, vous pouvez le faire tant que vous ne changez pas le contenu réel de la liste.

Ainsi, vous aurez besoin de faire une carte quelconque. Si vous utilisez une expression de générateur, [l'opération] sera effectuée au fur et à mesure que vous itérez et vous économiserez de la mémoire.

Skurmedel
la source
4

Vous pouvez faire quelque chose comme ceci:

a = [1,2,3,4,5]
b = [i**2 for i in a]

Cela s'appelle une compréhension de liste, pour vous permettre de faire une boucle dans une liste.

Nenoj
la source
3

La réponse donnée par Jemshit Iskenderov et Ignacio Vazquez-Abrams est vraiment bonne. Cela peut être illustré plus en détail avec cet exemple: imaginez que

a) Une liste avec deux vecteurs vous est donnée;

b) vous souhaitez parcourir la liste et inverser l'ordre de chacun des tableaux

Disons que vous avez

v = np.array([1, 2,3,4])
b = np.array([3,4,6])

for i in [v, b]:
    i = i[::-1]   # this command does not reverse the string

print([v,b])

Tu auras

[array([1, 2, 3, 4]), array([3, 4, 6])]

D'un autre côté, si vous faites

v = np.array([1, 2,3,4])
b = np.array([3,4,6])

for i in [v, b]:
   i[:] = i[::-1]   # this command reverses the string

print([v,b])

Le résultat est

[array([4, 3, 2, 1]), array([6, 4, 3])]
Rafael Monteiro
la source
1

Il n'est pas clair d'après votre question quels sont les critères pour décider des chaînes à supprimer, mais si vous avez ou pouvez faire une liste des chaînes que vous souhaitez supprimer, vous pouvez effectuer les opérations suivantes:

my_strings = ['a','b','c','d','e']
undesirable_strings = ['b','d']
for undesirable_string in undesirable_strings:
    for i in range(my_strings.count(undesirable_string)):
        my_strings.remove(undesirable_string)

qui change mes_chaînes en ['a', 'c', 'e']

Jorge
la source
0

Bref, faire des modifications sur la liste en itérant la même liste.

list[:] = ["Modify the list" for each_element in list "Condition Check"]

exemple:

list[:] = [list.remove(each_element) for each_element in list if each_element in ["data1", "data2"]]
siva balan
la source