Jak modyfikować pozycje na liście podczas wykonywania pętli for?

178

Teraz wiem, że modyfikowanie listy podczas iteracyjnego zapętlenia nie jest bezpieczne. Załóżmy jednak, że mam listę strun i chcę je usunąć. Czy zastąpienie wartości zmiennych liczy się jako modyfikacja?

Alex
źródło
20
Łańcuch nie tworzy wartości zmiennej.
user470379
4
@ user470379: To, czy elementy listy są modyfikowalne, nie ma znaczenia dla tego, czy modyfikowanie listy, na której się znajdują, jest bezpieczne, czy nie, podczas jej przeglądania.
martineau

Odpowiedzi:

144

Uważa się, że jest to kiepska forma. Zamiast tego użyj funkcji list składanych, z przypisaniem wycinka, jeśli chcesz zachować istniejące odniesienia do listy.

a = [1, 3, 5]
b = a
a[:] = [x + 2 for x in a]
print(b)
Ignacio Vazquez-Abrams
źródło
10
Przypisanie plasterków jest sprytne i pozwala uniknąć modyfikowania oryginału podczas pętli, ale wymaga utworzenia tymczasowej listy o długości oryginału.
Martineau
11
dlaczego przypisujemy b = a?
Vigrond
9
@Vigrond: Więc kiedy print binstrukcja jest wykonywana, możesz stwierdzić, czy azostała zmodyfikowana w miejscu, a nie zastąpiona. Inną możliwością byłoby print b is asprawdzenie, czy oba nadal odnoszą się do tego samego obiektu.
martineau
12
dlaczego [:] = a nie tylko a =?
kdubs
10
@kdubs: "... z przypisaniem plasterków, jeśli chcesz zachować istniejące odniesienia do listy."
Ignacio Vazquez-Abrams
162

Ponieważ pętla poniżej modyfikuje tylko elementy już widoczne, byłoby to akceptowalne:

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

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

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

Co różni się od:

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

ponieważ nie wymaga tworzenia tymczasowej listy i przypisywania jej w celu zastąpienia oryginału, chociaż wymaga więcej operacji indeksowania.

Uwaga: chociaż możesz modyfikować wpisy w ten sposób, nie możesz zmienić liczby pozycji w listbez ryzyka wystąpienia problemów.

Oto przykład tego, co mam na myśli - usunięcie wpisu zakłóca indeksowanie od tego momentu:

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!

(Wynik jest nieprawidłowy, ponieważ nie usunął wszystkich elementów, które powinien mieć).

Aktualizacja

Ponieważ jest to dość popularna odpowiedź, oto jak skutecznie usuwać wpisy „w miejscu” (nawet jeśli nie o to dokładnie chodzi):

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

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

print(b)  # -> ['a']  # CORRECT
martineau
źródło
3
Dlaczego jednak Python tworzy kopię tylko pojedynczego elementu w składni for i in a? Jest to bardzo sprzeczne z intuicją, pozornie różni się od innych języków i spowodowało błędy w moim kodzie, które musiałem debugować przez długi czas. Samouczek Pythona nawet o tym nie wspomina. Chociaż musi być jakiś powód?
xji
1
@JIXiang: Nie tworzy kopii. Po prostu przypisuje nazwę zmiennej pętli do kolejnych elementów lub wartości iterowanej rzeczy.
martineau
1
Eww, po co używać dwóch nazw ( a[i]i s) dla tego samego obiektu w tej samej linii, skoro nie musisz? Wolałbym to zrobić a[i] = a[i].strip().
Navin
3
@ Navin: Ponieważ a[i] = s.strip()wykonuje tylko jedną operację indeksowania.
martineau
1
@martineau enumerate(b)wykonuje operację indeksowania w każdej iteracji, a ty robisz kolejną z a[i] =. AFAIK niemożliwe jest zaimplementowanie tej pętli w Pythonie z tylko 1 operacją indeksowania na iterację pętli :(
Navin
18

Jeszcze jeden wariant pętli wygląda dla mnie czystszy niż wariant z 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
źródło
19
Wielu uważa, że ​​użycie czegoś takiego jak range(len(list))w Pythonie pachnie kodem.
martineau
2
@Reishin: Ponieważ enumeratejest generatorem, nie tworzy listy krotek, tworzy je pojedynczo podczas iteracji po liście. Jedynym sposobem, aby stwierdzić, który jest wolniejszy, jest timeit.
martineau
3
Kod @martineau może nie być zbyt ładny, ale według timeit enumeratejest wolniejszy
Reishin
2
@Reishin: Twój kod porównawczy nie jest całkowicie poprawny, ponieważ nie bierze pod uwagę potrzeby pobrania wartości z listy pod danym indeksem - co również nie jest pokazane w tej odpowiedzi.
martineau
4
@Reishin: Twoje porównanie jest nieważne właśnie z tego powodu. Mierzy pętlę na górze w izolacji. Aby być rozstrzygającym, czas potrzebny do wykonania całej pętli musi być zmierzony ze względu na możliwość, że wszelkie różnice narzutów mogą zostać złagodzone przez korzyści zapewniane kodowi wewnątrz pętli pętli w określony sposób - w przeciwnym razie nie porównujesz jabłek z jabłka.
martineau
11

Modyfikowanie każdego elementu podczas iteracji listy jest w porządku, o ile nie zmieniasz opcji dodawania / usuwania elementów do listy.

Możesz użyć rozumienia list:

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

lub po prostu wykonaj C-stylepętlę for:

for index, item in enumerate(l):
    l[index] = item.strip()
cizixs
źródło
4

Nie, nie zmieniłbyś „zawartości” listy, gdybyś mógł w ten sposób zmienić ciągi znaków. Ale w Pythonie nie można ich zmieniać. Każda operacja na łańcuchach zwraca nowy ciąg.

Gdybyś miał listę obiektów, o których wiedziałeś, że są zmienne, możesz to zrobić, o ile nie zmieniasz rzeczywistej zawartości listy.

Dlatego będziesz musiał zrobić jakąś mapę. Jeśli użyjesz wyrażenia generatora, to [operacja] zostanie wykonana podczas iteracji i zaoszczędzisz pamięć.

Skurmedel
źródło
4

Możesz zrobić coś takiego:

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

Nazywa się to rozumieniem list, aby ułatwić Ci zapętlenie wewnątrz listy.

Nenoj
źródło
3

Odpowiedź udzielona przez Jemshit Iskenderov i Ignacio Vazquez-Abrams jest naprawdę dobra. Można to dalej zilustrować następującym przykładem: wyobraź sobie to

a) Otrzymujesz listę z dwoma wektorami;

b) chciałbyś przejść przez listę i odwrócić kolejność każdej z tablic

Powiedzmy, że masz

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

Dostaniesz

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

Z drugiej strony, jeśli to zrobisz

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

Wynik to

[array([4, 3, 2, 1]), array([6, 4, 3])]
Rafael Monteiro
źródło
1

Z twojego pytania nie wynika jasno, jakie są kryteria decydujące o tym, jakie ciągi mają zostać usunięte, ale jeśli masz lub możesz utworzyć listę ciągów, które chcesz usunąć, możesz wykonać następujące czynności:

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)

co zmienia my_strings na ['a', 'c', 'e']

Jorge
źródło
0

W skrócie, aby dokonać modyfikacji na liście podczas iteracji tej samej listy.

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

przykład:

list[:] = [list.remove(each_element) for each_element in list if each_element in ["data1", "data2"]]
siva balan
źródło