Każdy, kto majstruje przy Pythonie wystarczająco długo, został ugryziony (lub rozdarty na kawałki) przez następujący problem:
def foo(a=[]):
a.append(5)
return a
Nowicjusze Python oczekiwałby to funkcja zawsze zwraca listę z tylko jednego elementu: [5]
. Rezultat jest natomiast zupełnie inny i bardzo zadziwiający (dla nowicjusza):
>>> foo()
[5]
>>> foo()
[5, 5]
>>> foo()
[5, 5, 5]
>>> foo()
[5, 5, 5, 5]
>>> foo()
Mój menedżer po raz pierwszy spotkał się z tą funkcją i nazwał ją „dramatyczną wadą projektową” języka. Odpowiedziałem, że zachowanie ma podstawowe wytłumaczenie i jest naprawdę bardzo zagadkowe i nieoczekiwane, jeśli nie rozumiesz elementów wewnętrznych. Nie byłem jednak w stanie odpowiedzieć (sobie) na następujące pytanie: jaki jest powód wiązania domyślnego argumentu przy definicji funkcji, a nie przy wykonywaniu funkcji? Wątpię, czy doświadczone zachowanie ma praktyczne zastosowanie (kto tak naprawdę używał zmiennych statycznych w C, bez powodowania błędów?)
Edytuj :
Baczek dał ciekawy przykład. Wraz z większością twoich komentarzy, w szczególności z Utaal, rozwinąłem dalej:
>>> def a():
... print("a executed")
... return []
...
>>>
>>> def b(x=a()):
... x.append(5)
... print(x)
...
a executed
>>> b()
[5]
>>> b()
[5, 5]
Wydaje mi się, że decyzja projektowa była związana z tym, gdzie umieścić zakres parametrów: wewnątrz funkcji czy „razem z nią”?
Wykonanie wiązania wewnątrz funkcji oznaczałoby, że x
jest skutecznie powiązane z określonym domyślnym, gdy funkcja jest wywoływana, nieokreślona, co może mieć głęboką wadę: def
linia byłaby „hybrydowa” w tym sensie, że część wiązania ( obiekt funkcji) miałby miejsce w momencie definicji, a część (przypisanie parametrów domyślnych) w czasie wywołania funkcji.
Rzeczywiste zachowanie jest bardziej spójne: wszystko tej linii jest oceniane podczas wykonywania tej linii, co oznacza przy definicji funkcji.
[5]
” Jestem początkującym Python i nie spodziewałbym się tego, bo oczywiście foo([1])
wróci [1, 5]
, nie [5]
. To, co chciałeś powiedzieć, to to, że nowicjusz oczekiwałby, że funkcja wywoływana bez parametru zawsze będzie zwracać [5]
.