Wie ändere ich Listeneinträge während der for-Schleife?

178

Jetzt weiß ich, dass es nicht sicher ist, die Liste während einer iterativen Schleife zu ändern. Angenommen, ich habe eine Liste von Zeichenfolgen und möchte die Zeichenfolgen selbst entfernen. Zählt das Ersetzen veränderlicher Werte als Änderung?

Alex
quelle
20
Ein String macht keinen veränderlichen Wert.
user470379
4
@ user470379: Ob die Elemente der Liste veränderbar sind, ist nicht relevant dafür, ob es sicher ist, die Liste, in der sie sich befinden, während des Durchlaufens zu ändern.
Martineau

Antworten:

144

Es wird als schlechte Form angesehen. Verwenden Sie stattdessen ein Listenverständnis mit Slice-Zuweisung, wenn Sie vorhandene Verweise auf die Liste beibehalten müssen.

a = [1, 3, 5]
b = a
a[:] = [x + 2 for x in a]
print(b)
Ignacio Vazquez-Abrams
quelle
10
Die Slice-Zuweisung ist clever und vermeidet das Ändern des Originals während der Schleife, erfordert jedoch die Erstellung einer temporären Liste mit der Länge des Originals.
Martineau
11
warum weisen wir b = a zu?
Vigrond
9
@Vigrond: Wenn die print bAnweisung ausgeführt wird, können Sie feststellen, ob sie aan Ort und Stelle geändert und nicht ersetzt wurde. Eine andere Möglichkeit wäre gewesen, print b is azu sehen, ob sich beide immer noch auf dasselbe Objekt beziehen.
Martineau
12
warum ein [:] = und nicht nur ein =?
kdubs
10
@kdubs: "... mit Slice-Zuweisung, wenn Sie vorhandene Verweise auf die Liste beibehalten müssen."
Ignacio Vazquez-Abrams
162

Da die folgende Schleife nur bereits gesehene Elemente ändert, wird dies als akzeptabel angesehen:

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

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

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

Welches unterscheidet sich von:

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

, dass keine temporäre Liste erstellt und zugewiesen werden muss, um das Original zu ersetzen, obwohl mehr Indizierungsvorgänge erforderlich sind.

Achtung: Obwohl Sie Einträge auf diese Weise ändern können, können Sie die Anzahl der Elemente in der nicht ändern, listohne das Risiko von Problemen zu riskieren.

Hier ist ein Beispiel dafür, was ich meine - das Löschen eines Eintrags bringt die Indizierung von diesem Punkt an durcheinander:

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!

(Das Ergebnis ist falsch, da nicht alle Elemente gelöscht wurden, die es haben sollte.)

Aktualisieren

Da dies eine ziemlich beliebte Antwort ist, können Sie Einträge wie folgt effektiv "an Ort und Stelle" löschen (auch wenn dies nicht genau die Frage ist):

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

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

print(b)  # -> ['a']  # CORRECT
Martineau
quelle
3
Warum erstellt Python jedoch nur eine Kopie des einzelnen Elements in der Syntax for i in a? Dies ist sehr eingängig, scheint sich von anderen Sprachen zu unterscheiden und hat zu Fehlern in meinem Code geführt, die ich über einen langen Zeitraum debuggen musste. Python Tutorial erwähnt es nicht einmal. Obwohl es einen Grund dafür geben muss?
Xji
1
@JIXiang: Es werden keine Kopien erstellt. Es weist lediglich den Namen der Schleifenvariablen aufeinanderfolgenden Elementen oder dem Wert des Objekts zu, auf das iteriert wird.
Martineau
1
Eww, warum zwei Namen ( a[i]und s) für dasselbe Objekt in derselben Zeile verwenden, wenn Sie nicht müssen? Ich würde es viel lieber tun a[i] = a[i].strip().
Navin
3
@ Navin: Weil a[i] = s.strip()nur eine Indizierungsoperation ausgeführt wird.
Martineau
1
@martineau enumerate(b)führt bei jeder Iteration eine Indizierungsoperation durch, mit der Sie eine weitere ausführen a[i] =. AFAIK es ist unmöglich, diese Schleife in Python mit nur 1 Indizierungsoperation pro Schleifeniteration zu implementieren :(
Navin
18

Eine für die Schleifenvariante sieht für mich sauberer aus als eine mit 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
Eugene Shatsky
quelle
19
Viele halten die Verwendung von range(len(list))Python für einen Codegeruch.
Martineau
2
@Reishin: Da enumeratees sich um einen Generator handelt, wird keine Liste von Tupeln erstellt. Sie werden einzeln erstellt, während die Liste durchlaufen wird. Der einzige Weg zu erkennen, was langsamer ist, wäre zu timeit.
Martineau
3
@martineau Code könnte nicht gut hübsch sein, aber laut timeit enumerateist langsamer
Reishin
2
@Reishin: Ihr Benchmarking-Code ist nicht vollständig gültig, da er nicht die Notwendigkeit berücksichtigt, den Wert in der Liste am angegebenen Index abzurufen - was auch in dieser Antwort nicht angezeigt wird.
Martineau
4
@Reishin: Ihr Vergleich ist genau aus diesem Grund ungültig. Es misst den Loop-Overhead isoliert. Um schlüssig zu sein, muss die Zeit gemessen werden, die die gesamte Schleife für die Ausführung benötigt, da die Möglichkeit besteht, dass Overhead-Unterschiede durch die Vorteile gemindert werden, die der Code innerhalb der Schleife für das Schleifen auf eine bestimmte Weise bietet - andernfalls vergleichen Sie Äpfel nicht mit Äpfel.
Martineau
11

Das Ändern jedes Elements während der Iteration einer Liste ist in Ordnung, solange Sie das Hinzufügen / Entfernen von Elementen zur Liste nicht ändern.

Sie können das Listenverständnis verwenden:

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

oder machen Sie einfach die C-stylefor-Schleife:

for index, item in enumerate(l):
    l[index] = item.strip()
Cizixs
quelle
4

Nein, Sie würden den "Inhalt" der Liste nicht ändern, wenn Sie Zeichenfolgen auf diese Weise mutieren könnten. Aber in Python sind sie nicht veränderlich. Jede Zeichenfolgenoperation gibt eine neue Zeichenfolge zurück.

Wenn Sie eine Liste von Objekten hatten, von denen Sie wussten, dass sie veränderbar sind, können Sie dies tun, solange Sie den tatsächlichen Inhalt der Liste nicht ändern.

Daher müssen Sie eine Karte erstellen. Wenn Sie einen Generatorausdruck verwenden, wird dieser [Vorgang] beim Iterieren ausgeführt und Sie sparen Speicherplatz.

Skurmedel
quelle
4

Sie können so etwas tun:

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

Es wird als Listenverständnis bezeichnet, um Ihnen das Durchlaufen einer Liste zu erleichtern.

Nenoj
quelle
3

Die Antwort von Jemshit Iskenderov und Ignacio Vazquez-Abrams ist wirklich gut. Dies lässt sich anhand dieses Beispiels weiter veranschaulichen: Stellen Sie sich das vor

a) Sie erhalten eine Liste mit zwei Vektoren.

b) Sie möchten die Liste durchlaufen und die Reihenfolge der einzelnen Arrays umkehren

Nehmen wir an, Sie haben

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

Sie erhalten

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

Auf der anderen Seite, wenn Sie dies tun

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

Das Ergebnis ist

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

Aus Ihrer Frage geht nicht hervor, nach welchen Kriterien entschieden werden soll, welche Zeichenfolgen entfernt werden sollen. Wenn Sie jedoch eine Liste der Zeichenfolgen haben oder erstellen können, die Sie entfernen möchten, können Sie Folgendes tun:

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)

was my_strings in ['a', 'c', 'e'] ändert

Jorge
quelle
0

Kurz gesagt, um Änderungen an der Liste vorzunehmen, während dieselbe Liste wiederholt wird.

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

Beispiel:

list[:] = [list.remove(each_element) for each_element in list if each_element in ["data1", "data2"]]
Siva Balan
quelle