Wiem, że domyślne argumenty są tworzone w czasie inicjalizacji funkcji, a nie za każdym razem, gdy funkcja jest wywoływana. Zobacz następujący kod:
def ook (item, lst=[]):
lst.append(item)
print 'ook', lst
def eek (item, lst=None):
if lst is None: lst = []
lst.append(item)
print 'eek', lst
max = 3
for x in xrange(max):
ook(x)
for x in xrange(max):
eek(x)
Nie rozumiem, dlaczego zostało to zaimplementowane w ten sposób. Jakie korzyści oferuje to zachowanie w stosunku do inicjalizacji za każdym razem?
Odpowiedzi:
Myślę, że powodem jest prostota implementacji. Pozwól mi rozwinąć.
Domyślną wartością funkcji jest wyrażenie, które należy ocenić. W twoim przypadku jest to proste wyrażenie, które nie zależy od zamknięcia, ale może być czymś, co zawiera wolne zmienne -
def ook(item, lst = something.defaultList())
. Jeśli chcesz zaprojektować Python, będziesz miał wybór - czy oceniasz go raz, kiedy funkcja jest zdefiniowana, czy za każdym razem, gdy funkcja jest wywoływana. Python wybiera pierwszą (w przeciwieństwie do Ruby, która idzie z drugą opcją).Są z tego pewne korzyści.
Po pierwsze, masz trochę przyspieszenia i zwiększenia pamięci. W większości przypadków będziesz mieć niezmienne domyślne argumenty, a Python może je zbudować tylko raz, zamiast przy każdym wywołaniu funkcji. To oszczędza (trochę) pamięć i czas. Oczywiście nie działa to dobrze ze zmiennymi wartościami, ale wiesz, jak się poruszać.
Kolejną zaletą jest prostota. Łatwo jest zrozumieć, jak ocenia się to wyrażenie - korzysta z zakresu leksykalnego, gdy funkcja jest zdefiniowana. Jeśli pójdą w drugą stronę, zakres leksykalny może się zmieniać między definicją a wywołaniem, co utrudni debugowanie. W takich przypadkach Python przechodzi bardzo długą drogę.
źródło
Jednym ze sposobów jest to, że parametr
lst.append(item)
nie powoduje mutacjilst
.lst
wciąż odwołuje się do tej samej listy. Po prostu treść tej listy została zmutowana.Zasadniczo Python nie ma (o ile pamiętam) żadnych zmiennych stałych ani niezmiennych - ale ma pewne stałe, niezmienne typy. Nie możesz zmodyfikować wartości całkowitej, możesz ją tylko zastąpić. Ale możesz modyfikować zawartość listy bez jej zastępowania.
Podobnie jak liczba całkowita, nie możesz modyfikować odwołania, możesz je tylko zastąpić. Możesz jednak zmodyfikować zawartość obiektu, do którego się odwołujesz.
Jeśli chodzi o jednorazowe utworzenie domyślnego obiektu, to wyobrażam sobie, że jest to głównie optymalizacja, aby zaoszczędzić na tworzeniu obiektów i narzucaniu śmieci.
źródło
Pozwala wybrać pożądane zachowanie, jak pokazano w przykładzie. Jeśli więc chcesz, aby domyślny argument był niezmienny, użyj niezmiennej wartości , takiej jak
None
lub1
. Jeśli chcesz zmienić domyślny argument na zmienny, użyj czegoś zmiennego, takiego jak[]
. To tylko elastyczność, choć trzeba przyznać, że może gryźć, jeśli jej nie znasz.źródło
Myślę, że prawdziwa odpowiedź brzmi: Python został napisany jako język proceduralny i dopiero później przyjął aspekty funkcjonalne. To, czego szukasz, to domyślne ustawienie parametru jako zamknięcie, a zamknięcia w Pythonie są tak naprawdę tylko częściowo wypalone. Na dowód tej próby:
co daje miejsce, w
[2, 2, 2]
którym można oczekiwać prawdziwego zamknięcia[0, 1, 2]
.Jest całkiem sporo rzeczy, które chciałbym, gdyby Python miał możliwość zawijania domyślnych parametrów w zamknięciach. Na przykład:
Byłoby to bardzo przydatne, ale „a” nie jest objęte zakresem definicji funkcji, więc nie możesz tego zrobić, a zamiast tego musisz robić niezręczne:
Co jest prawie takie samo ... prawie.
źródło
Ogromną zaletą jest zapamiętywanie. To jest standardowy przykład:
i dla porównania:
Pomiary czasu w ipython:
źródło
Dzieje się tak, ponieważ kompilacja w Pythonie jest wykonywana przez wykonanie kodu opisowego.
Jeśli ktoś tak powiedział
byłoby całkiem jasne, że za każdym razem chciałeś nowej tablicy.
Ale co jeśli powiem:
Tutaj zgaduję, że chcę tworzyć rzeczy na różnych listach i mieć jeden globalny catch-all, gdy nie podam listy.
Ale jak zgadłby to kompilator? Po co więc próbować? Moglibyśmy polegać na tym, czy to się nazywa, czy nie, i czasem może to pomóc, ale tak naprawdę to tylko zgadywanie. Jednocześnie istnieje dobry powód, aby nie próbować - spójności.
W tej chwili Python po prostu wykonuje kod. Zmienna list_of_all jest już przypisana do obiektu, więc obiekt jest przekazywany przez odwołanie do kodu, który domyślnie x, w taki sam sposób, jak wywołanie dowolnej funkcji otrzyma odwołanie do obiektu lokalnego o nazwie tutaj.
Gdybyśmy chcieli odróżnić nienazwany od nazwanego przypadku, wymagałoby to kodu przy kompilacji wykonującego przypisanie w znacznie inny sposób niż jest wykonywany w czasie wykonywania. Dlatego nie robimy specjalnego przypadku.
źródło
Dzieje się tak, ponieważ funkcje w Pythonie są obiektami pierwszej klasy :
Wyjaśnia dalej, że edycja wartości parametru modyfikuje wartość domyślną dla kolejnych wywołań i że proste rozwiązanie polegające na użyciu None jako wartości domyślnej, z jawnym testem w treści funkcji, jest wszystkim, co jest potrzebne, aby zapewnić brak niespodzianek.
Co oznacza, że po
def foo(l=[])
wywołaniu staje się instancją tej funkcji i jest ponownie wykorzystywana do dalszych wywołań. Pomyśl o parametrach funkcji jako o oddzielaniu atrybutów obiektu.Pro może obejmować wykorzystanie tego, aby klasy miały zmienne statyczne podobne do C. Dlatego najlepiej zadeklarować wartości domyślne Brak i zainicjować je w razie potrzeby:
daje:
zamiast nieoczekiwanego:
źródło