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?


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)

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

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

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()

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.


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.


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])]

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']


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"]]
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.