Zrozumienia mają nieoczekiwane interakcje z określaniem zakresu. Czy to jest oczekiwane zachowanie?
Mam metodę:
def leave_room(self, uid):
u = self.user_by_id(uid)
r = self.rooms[u.rid]
other_uids = [ouid for ouid in r.users_by_id.keys() if ouid != u.uid]
other_us = [self.user_by_id(uid) for uid in other_uids]
r.remove_user(uid) # OOPS! uid has been re-bound by the list comprehension above
# Interestingly, it's rebound to the last uid in the list, so the error only shows
# up when len > 1
Ryzykując marudzenie, jest to brutalne źródło błędów. Kiedy piszę nowy kod, od czasu do czasu znajduję bardzo dziwne błędy spowodowane ponownym wiązaniem - nawet teraz, gdy wiem, że to problem. Muszę utworzyć regułę typu „zawsze poprzedzać zmienne tymczasowe w listach składanych z podkreśleniem”, ale nawet to nie jest niezawodne.
Fakt, że czeka na nas ta losowa bomba zegarowa, w pewnym sensie neguje całą przyjemną „łatwość użycia” składania list.
python
binding
list-comprehension
Jabavu Adams
źródło
źródło
for
-loop konstruktem ifor
-loops zmienne wycieków . Nie było to więc wyraźne, ale zostało to stwierdzone w sposób dorozumiany.Odpowiedzi:
Listy składane powodują wyciek zmiennej sterującej pętli w Pythonie 2, ale nie w Pythonie 3. Oto Guido van Rossum (twórca Pythona) wyjaśniający historię tego:
źródło
break
- ale nie ma znaczenia dla zrozumienia. Przypominam sobie dyskusje o comp.lang.python, w których ludzie chcieli przypisać zmienne w środku wyrażenia. Mniej szalony sposób, że było jedno-wartość dla klauzule np.sum100 = [s for s in [0] for i in range(1, 101) for s in [s + i]][-1]
, ale potrzebuje tylko zmiennej lokalnej dla zrozumienia i działa równie dobrze w Pythonie 3. Myślę, że „wyciek” był jedynym sposobem na ustawienie zmiennej widocznej poza wyrażeniem. Wszyscy zgodzili się, że te techniki są okropne :-)Tak, wyrażenia listowe „przeciekają” swoją zmienną w Pythonie 2.x, podobnie jak pętle for.
Z perspektywy czasu uznano to za błąd i uniknięto go dzięki wyrażeniom generatora. EDYCJA: Jak zauważa Matt B., uniknięto tego również, gdy składnie zestawu i słownika zostały przeniesione z Pythona 3.
Zachowanie list składanych musiało pozostać takie, jak w Pythonie 2, ale zostało w pełni naprawione w Pythonie 3.
Oznacza to, że we wszystkich:
x
jest zawsze lokalne dla wyrażenia podczas nich:w Pythonie 2.x wszystkie przeciekają
x
zmienną do otaczającego zakresu.UPDATE for Python 3.8 (?) : PEP 572 wprowadzi
:=
operator przypisania, który celowo wycieka ze zrozumień i wyrażeń generatora! Motywują go zasadniczo 2 przypadki użycia: przechwytywanie „świadka” funkcji wczesnego kończenia, takich jakany()
iall()
:i aktualizowanie stanu mutowalnego:
Patrz Dodatek B do dokładnego określania zakresu. Zmienna jest przypisywana w najbliższym otoczeniu
def
lublambda
, chyba że funkcja deklaruje tononlocal
lubglobal
.źródło
Tak, przypisanie następuje tam, tak jak w
for
pętli. Nie jest tworzony żaden nowy zakres.Jest to zdecydowanie oczekiwane zachowanie: w każdym cyklu wartość jest przypisywana podanej nazwie. Na przykład,
Gdy zostanie to rozpoznane, wydaje się łatwe do uniknięcia: nie używaj istniejących nazw dla zmiennych w ramach pojmowania.
źródło
Co ciekawe, nie ma to wpływu na słownik ani zestawy pojęć.
Jednak zostało to naprawione w 3, jak wspomniano powyżej.
źródło
pewne obejście dla Pythona 2.6, gdy takie zachowanie nie jest pożądane
źródło
W python3, gdy jest w trybie listowym, zmienna nie zmienia się po zakończeniu jej zakresu, ale kiedy używamy prostej pętli for, zmienna zostaje ponownie przypisana poza zakres.
i = 1 print (i) print ([i w zakresie (5)]) print (i) Wartość i pozostanie tylko 1.
Teraz po prostu użyj pętli for, a wartość i zostanie ponownie przypisana.
źródło