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?
@ 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.
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
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
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 timeitenumeratejest 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()
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ęć.
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 stringprint([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 stringprint([v,b])
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)
Odpowiedzi:
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.
źródło
print b
instrukcja jest wykonywana, możesz stwierdzić, czya
została zmodyfikowana w miejscu, a nie zastąpiona. Inną możliwością byłobyprint b is a
sprawdzenie, czy oba nadal odnoszą się do tego samego obiektu.Ponieważ pętla poniżej modyfikuje tylko elementy już widoczne, byłoby to akceptowalne:
Co różni się od:
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
list
bez ryzyka wystąpienia problemów.Oto przykład tego, co mam na myśli - usunięcie wpisu zakłóca indeksowanie od tego momentu:
(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):
źródło
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?a[i]
is
) dla tego samego obiektu w tej samej linii, skoro nie musisz? Wolałbym to zrobića[i] = a[i].strip()
.a[i] = s.strip()
wykonuje tylko jedną operację indeksowania.enumerate(b)
wykonuje operację indeksowania w każdej iteracji, a ty robisz kolejną za[i] =
. AFAIK niemożliwe jest zaimplementowanie tej pętli w Pythonie z tylko 1 operacją indeksowania na iterację pętli :(Jeszcze jeden wariant pętli wygląda dla mnie czystszy niż wariant z enumerate ():
źródło
range(len(list))
w Pythonie pachnie kodem.enumerate
jest generatorem, nie tworzy listy krotek, tworzy je pojedynczo podczas iteracji po liście. Jedynym sposobem, aby stwierdzić, który jest wolniejszy, jesttimeit
.timeit
enumerate
jest wolniejszyModyfikowanie 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:
lub po prostu wykonaj
C-style
pętlę for:źródło
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ęć.
źródło
Możesz zrobić coś takiego:
Nazywa się to rozumieniem list, aby ułatwić Ci zapętlenie wewnątrz listy.
źródło
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
Dostaniesz
Z drugiej strony, jeśli to zrobisz
Wynik to
źródło
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:
co zmienia my_strings na ['a', 'c', 'e']
źródło
W skrócie, aby dokonać modyfikacji na liście podczas iteracji tej samej listy.
przykład:
źródło