Każdy, kto drużyna z Python wystarczająco długo został ugryziony (lub rozdarty na kawałki) według następującego wydania:

def foo(a=[]):
    a.append(5)
    return a

Nowicjuszy Pythona oczekiwałyby tej funkcji zawsze zwrócić listę tylko jednym elementem: [5]. Wynik jest bardzo inny i bardzo zadziwiający (dla początkującego):

>>> foo()
[5]
>>> foo()
[5, 5]
>>> foo()
[5, 5, 5]
>>> foo()
[5, 5, 5, 5]
>>> foo()

Menedżer kopalni kiedyś miał pierwsze spotkanie z tą funkcją i nazwałem go "dramatyczną wadą projektową" języka. Odpowiedziałem, że zachowanie miało podstawowe wyjaśnienie i jest rzeczywiście bardzo zagadkowe i nieoczekiwane, jeśli nie rozumiesz internali. Jednak nie byłbym w stanie odpowiedzieć (do siebie) następujące pytanie: Jaki jest powód wiązania domyślnego argumentu w definicji funkcji, a nie w funkcji wykonania? Wątpię, by doświadczone zachowanie ma praktyczne zastosowanie (który naprawdę używał zmiennych statycznych w C, bez hodowli?)

Edytuj :

Baczek zrobił ciekawy przykład. W szczególności większość twoich komentarzy i Utaala, opracowałem dalej:

>>> def a():
...     print("a executed")
...     return []
... 
>>>            
>>> def b(x=a()):
...     x.append(5)
...     print(x)
... 
a executed
>>> b()
[5]
>>> b()
[5, 5]

Dla mnie wydaje się, że decyzja projektowa była w stosunku do miejsca, w stosunku do wprowadzenia zakresu parametrów: wewnątrz funkcji lub "razem" z nim?

Robiąc wiązanie wewnątrz funkcji oznaczałoby to, że x jest skutecznie związany z określonym domyślnym, gdy funkcja zostanie wywołana, nie zdefiniowana, coś, co stanowiłoby głęboką wadę: linia def byłaby "hybryd" W tym sensie, że część wiązania (obiektu funkcji) nastąpi w definicji i części (przypisanie parametrów domyślnych) w czasie wywołania funkcji.

Rzeczywiste zachowanie jest bardziej spójne: wszystko w tej linii zostaje ocenione, gdy ta linia zostanie wykonana, znaczenie w definicji funkcji.

3013
Stefano Borini 15 lipiec 2009, 22:00

27 odpowiedzi

Najlepsza odpowiedź

Właściwie to nie jest wada projektowa i nie jest to z powodu internali lub wydajności.
Jest to po prostu z faktu, że funkcje w Pythonie są obiektami pierwszej klasy, a nie tylko kawałek kodu.

Jak tylko będziesz myśleć w ten sposób, to całkowicie ma sens: funkcja jest przedmiotem ocenianym na jego definicji; Domyślne parametry są rodzajami "danych członków" i dlatego ich stan może się zmienić z jednego połączenia do drugiego - dokładnie tak w każdym innym obiekcie.

W każdym razie Effbot ma bardzo ładne wyjaśnienie przyczyn tego zachowania w domyślne wartości parametrów w Pythonie .
Znalazłem to bardzo jasne, a ja naprawdę sugeruję czytać go na lepszą wiedzę o tym, jak działa obiekty funkcyjne.

1676
Mark Omo 2 grudzień 2020, 04:55

Odpowiednia część Dokumentacja:

Domyślne wartości parametrów są oceniane od lewej do prawej, gdy definicja funkcji jest wykonywana. Oznacza to, że wyrażenie jest oceniane raz, gdy funkcja jest zdefiniowana, a ta sama wartość "wstępnie obliczona" jest używana dla każdego połączenia. Jest to szczególnie ważne, aby zrozumieć, kiedy domyślny parametr jest niezmiennym obiektem, taki jak lista lub słownik: Jeśli funkcja modyfikuje obiekt (np. Włączając element do listy), wartość domyślna jest w efekcie modyfikowana. Na ogół nie jest to, co było przeznaczone. Wokoło tego jest użycie None jako domyślnego i jawnie testuj go w ciele funkcji, np.:

def whats_on_the_telly(penguin=None):
    if penguin is None:
        penguin = []
    penguin.append("property of the zoo")
    return penguin
248
Boris 30 styczeń 2020, 18:00

Nie wiem nic o wewnętrznych działaniach z tłumaczem Pythona (i nie jestem ekspertem w kompilatorach i tłumaczeach), więc nie obwiniaj mnie, jeśli proponuję coś niezawodniczego lub niemożliwe.

Pod warunkiem, że obiekty Pythona są niezmienione Myślę, że należy to wziąć pod uwagę przy projektowaniu domyślnych argumentów. Po utworzeniu listy:

a = []

Spodziewasz się, że otrzymasz listę Nowy , do którego odwołuje się a.

Dlaczego powinno być a=[]

def x(a=[]):

Instancjonuj nową listę definicji funkcji, a nie na wywołanie? Tak jak pytasz "Jeśli użytkownik nie poddajesz argumentu, a następnie instancja nowa lista i użyj go tak, jakby został wyprodukowany przez dzwoniącego". Myślę, że to jest niejednoznaczne zamiast tego:

def x(a=datetime.datetime.now()):

Użytkownik, czy chcesz a, aby ustawić domyślne do datetime odpowiadającego podczas definiowania lub wykonania x? W tym przypadku, podobnie jak w poprzednim, zachowam to samo zachowanie, jakby domyślny argument "przypisanie" był pierwszą instrukcją funkcji (datetime.now() wywoływaną w wywołaniu funkcji). Z drugiej strony, jeśli użytkownik chciał mapowania definicji, mógł napisać:

b = datetime.datetime.now()
def x(a=b):

Wiem, wiem: To jest zamknięcie. Alternatywnie Python może podać słowo kluczowe, aby wymusić wiązanie definicji:

def x(static a=b):
121
Georgy 9 maj 2019, 09:15

Cóż, powód jest dość po prostu, że powiązania są wykonywane, gdy kod jest wykonywany, a definicja funkcji jest wykonywana, cóż ... po zdefiniowaniu funkcji.

Porównaj to:

class BananaBunch:
    bananas = []

    def addBanana(self, banana):
        self.bananas.append(banana)

Ten kod cierpi z powodu dokładnie tego samego nieoczekiwanego sytuacji. Banany to atrybut klasy, a tym samym, kiedy dodasz do niego rzeczy, jest dodawany do wszystkich instancji tej klasy. Powód jest dokładnie taki sam.

To tylko "jak to działa" i czyniąc to inaczej w przypadku funkcji, prawdopodobnie byłby skomplikowany, aw przypadku klasy prawdopodobnie niemożliwe lub przynajmniej spowolnić instancję obiektów, jak będzie musiał zachować kod klasy i wykonaj go, gdy obiekty są tworzone.

Tak, jest nieoczekiwany. Ale kiedy pensów spada, idealnie pasuje do tego, jak Python działa ogólnie. W rzeczywistości jest to dobra pomoc dydaktyczna, a kiedy rozumiesz, dlaczego tak się dzieje, grok Python znacznie lepiej.

To powiedział, że powinno być widoczne w dowolnym samouczkach Pythona. Ponieważ jak wspominasz, wszyscy prowadzą do tego problemu prędzej czy później.

83
Lennart Regebro 19 grudzień 2014, 22:53

Dlaczego nie introspektujesz?

Naprawdę naprawdę Nikt nie wykonał wnikliwej introspekcji oferowanej przez Pythona ({X0}} i 3 mają zastosowanie).

Biorąc pod uwagę prostą małą funkcję func zdefiniowane jako:

>>> def func(a = []):
...    a.append(5)

Kiedy Python spotyka go, pierwszą rzeczą, którą zrobi, jest skompilować go w celu utworzenia obiektu code dla tej funkcji. Podczas gdy ten krok kompilacji jest wykonywany, Python ocenia *, a następnie sklepy Domyślne argumenty (pusta lista [] tutaj) w samym obiekcie funkcyjnej . Jak wspomniano o najwyższej odpowiedzi: lista a może być teraz uważana za członka funkcji func.

Zróbmy pewną introspekcję, przed i po zbadaniu, jak lista zostanie rozszerzona wewnątrz obiekt funkcyjny. Używam Python 3.x dla tego, dla Pythona 2 to samo dotyczy (użyj __defaults__ lub func_defaults w Pythonie 2; Tak, dwie nazwy dla tej samej rzeczy).

Funkcja przed wykonaniem:

>>> def func(a = []):
...     a.append(5)
...     

Po tym, jak Python wykonuje tę definicję, podjęto tutaj dowolne domyślne parametry (a = [] tutaj) i Cram im w atrybucie __defaults__ dla obiektu funkcyjnego (odpowiednia sekcja: CABLES):

>>> func.__defaults__
([],)

O.K, więc pusta lista jako pojedynczy wpis w {x0}}, tak jak oczekiwano.

Funkcja po wykonaniu:

Teraz wykonajmy tę funkcję:

>>> func()

Teraz zobaczmy te __defaults__ ponownie:

>>> func.__defaults__
([5],)

zdziwiony? wartość wewnątrz zmian obiektu! Kolejne połączenia z funkcją po prostu po prostu dołącza do tego osadzenia {X0}} Obiekt:

>>> func(); func(); func()
>>> func.__defaults__
([5, 5, 5, 5],)

Więc masz to, powód, dla którego ta wkładka "ma miejsce, jest dlatego, że domyślne argumenty są częścią obiektu funkcyjnego. Nic tu nie ma dziwnego, wszystko jest trochę zaskakujące.

Wspólne rozwiązanie zwalczania jest to używanie None jako domyślne, a następnie zainicjowanie w korpusie funkcji:

def func(a = None):
    # or: a = [] if a is None else a
    if a is None:
        a = []

Ponieważ korpus funkcji jest wykonywany przez cały czas, zawsze otrzymujesz nową nową pustą listę, jeśli nie zostanie przekazany żaden argument na a.


Aby jeszcze bardziej sprawdzić, czy lista w __defaults__ jest taka sama, jak używana w funkcji func Możesz po prostu zmienić funkcję, aby zwrócić id listy a używany w środku korpus funkcyjny. Następnie porównaj go na liście w __defaults__ (Pozycja [0] w __defaults__), a zobaczysz, jak są one odnoszące się do tej samej instancji:

>>> def func(a = []): 
...     a.append(5)
...     return id(a)
>>>
>>> id(func.__defaults__[0]) == func()
True

Wszystko z mocą introspekcji!


*

def bar(a=input('Did you just see me without calling the function?')): 
    pass  # use raw_input in Py2

Jak zauważysz, input() zostanie wywołany przed procesem budowania funkcji i wiązania go do nazwy bar.

73
Toby Speight 11 październik 2018, 16:33

Kiedyś myślałem, że tworzenie obiektów w czasie wykonywania byłoby lepsze podejście. Teraz jestem mniej pewien, ponieważ tracisz kilka przydatnych funkcji, choć może być warto, niezależnie od tego, aby zapobiec nieporozumieniu nowości. Wady robienia tego są:

1. Wydajność

def foo(arg=something_expensive_to_compute())):
    ...

Jeśli wykorzystywana jest ocena połączeń, ta droga funkcja jest wywoływana za każdym razem, gdy funkcja jest używana bez argumentu. Albo zapłaciłbyś drogie cenę na każdym wywołaniu lub trzeba ręcznie podręczyć wartość zewnętrznie, zanieczyszczając przestrzeń nazw i dodając stylistę.

2. Wymuszanie parametrów związanych

Przydatną sztuczką jest związanie parametrów lambda do prądu Bieżąca wiązanie zmiennej po utworzeniu Lambda. Na przykład:

funcs = [ lambda i=i: i for i in range(10)]

Zwraca listę funkcji, które zwracają odpowiednio 0,1,2,3 ... Jeśli zachowanie zostanie zmienione, zamiast tego wiążą i do wartości I, więc otrzymasz listę funkcji, które zwracały 9.

Jedynym sposobem na wdrożenie tego w przeciwnym razie byłoby stworzenie dalszego zamknięcia z związanym, tj.:

def make_func(i): return lambda: i
funcs = [make_func(i) for i in range(10)]

3. Introspekcja

Rozważ kod:

def foo(a='test', b=100, c=[]):
   print a,b,c

Możemy uzyskać informacje o argumentach i domyślnych za pomocą modułu inspect, które

>>> inspect.getargspec(foo)
(['a', 'b', 'c'], None, None, ('test', 100, []))

Informacje te są bardzo przydatne dla rzeczy, takich jak generowanie dokumentów, metaprogramowanie, dekoratory itp.

Załóżmy teraz, że zachowanie ustawień domyślnych można zmienić tak, że jest to równowartość:

_undefined = object()  # sentinel value

def foo(a=_undefined, b=_undefined, c=_undefined)
    if a is _undefined: a='test'
    if b is _undefined: b=100
    if c is _undefined: c=[]

Jednak straciliśmy zdolność do introspekcji i zobaczyć, jakie domyślne argumenty . Ponieważ obiekty nie zostały skonstruowane, nie możemy ich zdobyć, nie wzywając funkcji. Najlepsze, co moglibyśmy zrobić, to przechowywać z kodu źródłowego i zwrócić tak jako łańcuch.

60
Brian 16 lipiec 2009, 19:13

5 punktów w obronie Pythona

  1. prostota : zachowanie jest proste w następnym sensie: Większość ludzi wpada w tę pułapkę tylko raz, nie kilka razy.

  2. spójność : python zawsze przechodzi obiekty, a nie nazwy. Domyślny parametr jest oczywiście część funkcji Kierunek (nie jest korpus funkcji). Dlatego należy ocenić w czasie ładowania modułu (i tylko w czasie ładowania modułu, chyba że zagnieżdżona), nie w czasie wywołania funkcji.

  3. przydatność : jak Frederik Lundh wskazuje na jego wyjaśnienie z "Domyślne wartości parametrów w Pythonie", The Aktualne zachowanie może być dość przydatne do zaawansowanego programowania. (Użyj oszczędnie.)

  4. wystarczająca dokumentacja : W najbardziej podstawowej dokumentacji Python, Tutorial, problem jest głośno ogłoszony jako "ważne ostrzeżenie" w pierwszej podsekcja sekcji "Więcej na temat definiowania funkcji". Ostrzeżenie nawet używa Boldface, który rzadko jest stosowany poza nagłówkami. RTFM: Przeczytaj drobną instrukcję.

  5. Meta-Learning : Wpadnięcie do pułapki jest naprawdę bardzo Pomocny moment (przynajmniej jeśli jesteś refleksyjnym uczniem), Ponieważ później lepiej zrozumiesz punkt "Spójność" powyżej i to będzie Naucz Cię o Pythonie.

57
jakubde 21 styczeń 2021, 12:41

To zachowanie jest łatwe wyjaśnione przez:

  1. Deklaracja funkcji (klasa etc.) jest wykonywana tylko raz, tworząc wszystkie obiekty wartości domyślnej
  2. Wszystko jest przekazywane przez odniesienie

Więc:

def x(a=0, b=[], c=[], d=0):
    a = a + 1
    b = b + [1]
    c.append(1)
    print a, b, c
  1. a nie zmienia się - każda przydziałowa wywołania tworzy nowy obiekt INT - Nowy obiekt jest drukowany
  2. b nie zmienia się - Nowa tablica jest budowana z wartości domyślnej i drukowana
  3. c Zmiany - Obsługa jest wykonywana na tym samym obiekcie - i jest drukowana
53
Dimitris Fasarakis Hilliard 24 październik 2017, 06:34

Pytasz, dlaczego to:

def func(a=[], b = 2):
    pass

Nie jest technicznie równoważny:

def func(a=None, b = None):
    a_default = lambda: []
    b_default = lambda: 2
    def actual_func(a=None, b=None):
        if a is None: a = a_default()
        if b is None: b = b_default()
    return actual_func
func = func()

Z wyjątkiem przypadku jawnej nazywania funkcji FUNC (Brak, None), które zignorujemy.

Innymi słowy, zamiast oceniać domyślne parametry, dlaczego nie przechowywać każdego z nich i ocenić je, gdy funkcja jest nazywana?

Jedna odpowiedź jest prawdopodobnie tam - skutecznie obróciłoby każdą funkcję z domyślnymi parametrami do zamknięcia. Nawet jeśli wszystko jest ukryte w tłumaczeniu, a nie w pełnym zamknięciu, dane muszą być przechowywane gdzieś. Byłoby wolniejsze i użyj więcej pamięci.

35
Glenn Maynard 15 lipiec 2009, 20:18

1) Tzw. Problem "Mutable Domyślny argument" jest ogólnie specjalnym przykładem wykazującym, że:
"Wszystkie funkcje z tym problemem cierpią również z podobnego problemu skutków ubocznych na rzeczywistym parametrze ,"
To jest sprzeczne z zasadami programowania funkcjonalnego, zwykle niepożądane i powinny być naprawione zarówno razem.

Przykład:

def foo(a=[]):                 # the same problematic function
    a.append(5)
    return a

>>> somevar = [1, 2]           # an example without a default parameter
>>> foo(somevar)
[1, 2, 5]
>>> somevar
[1, 2, 5]                      # usually expected [1, 2]

Rozwiązanie : kopia
Absolutnie bezpiecznym rozwiązaniem jest copy lub deepcopy pierwszy obiekt wejściowy, a następnie zrobić wszystko z kopią.

def foo(a=[]):
    a = a[:]     # a copy
    a.append(5)
    return a     # or everything safe by one line: "return a + [5]"

Wiele wbudowanych znanych typów ma metodę kopiowania, jak some_dict.copy() lub some_set.copy() lub może być kopiowany łatwy jak somelist[:] lub list(some_list). Każdy obiekt może być również kopiowany przez copy.copy(any_object) lub dokładniej przez copy.deepcopy() (ten ostatni przydatny, jeśli goutable obiekt składa się z niewielkich obiektów). Niektóre obiekty są zasadniczo oparte na skutkach ubocznych, takich jak "Plik" obiekt i nie mogą być znacząco reprodukowane przez kopię. Kopiowanie

Przykładowy problem dla Podobnie jak pytanie

class Test(object):            # the original problematic class
  def __init__(self, var1=[]):
    self._var1 = var1

somevar = [1, 2]               # an example without a default parameter
t1 = Test(somevar)
t2 = Test(somevar)
t1._var1.append([1])
print somevar                  # [1, 2, [1]] but usually expected [1, 2]
print t2._var1                 # [1, 2, [1]] but usually expected [1, 2]

Nie powinno być zapisywane w atrybucie Public Public instancji zwróconej przez tę funkcję. (Zakładając, że atrybuty instancji prywatne nie powinny być modyfikowane z zewnątrz tej klasy lub podklasy przez konwencję. I.e. {x0}} to prywatny atrybut)

Wniosek:
. Parametry wejściowe obiekty nie powinny być modyfikowane na miejscu (zmutowane) ani nie należy ich wiązać z obiektem zwracanym przez funkcję. (Jeśli preferujemy programowanie bez skutków ubocznych, które są silnie zalecane. Patrz Wiki O "Efekt uboczny" (Pierwsze dwa akapity mają znaczenie w tym kontekście.) .)

2)
. Tylko wtedy, gdy efekt uboczny na rzeczywistym parametrze jest wymagany, ale niechciany na parametrze domyślnym, to przydatnym rozwiązaniem jest def ...(var1=None): if var1 is None: {x2}} var1 = [] Mutable zachowanie domyślnych parametrów przydatnych.

35
Community 23 maj 2017, 11:47

Właściwie to nie ma nic wspólnego z wartościami domyślnymi, innymi niż to często pojawia się w odniesieniu do nieoczekiwanych zachowań, gdy zapisujesz funkcje z nieznacznymi wartościami domyślnymi.

>>> def foo(a):
    a.append(5)
    print a

>>> a  = [5]
>>> foo(a)
[5, 5]
>>> foo(a)
[5, 5, 5]
>>> foo(a)
[5, 5, 5, 5]
>>> foo(a)
[5, 5, 5, 5, 5]

Brak wartości domyślnych w tym kodzie, ale masz dokładnie ten sam problem.

Problem polega na tym, że foo jest modyfikowanie Zmienna zmienna przekazana z rozmówcy, gdy dzwoniący nie spodziewa się tego. Kod jakby byłby w porządku, jeśli funkcja została nazwana czymś takim jak append_5; Wtedy dzwoniący będzie dzwonił do funkcji, aby zmodyfikować wartość, w której przechodzą, a zachowanie można się spodziewać. Ale taka funkcja byłaby bardzo mało prawdopodobne, aby wziąć domyślny argument, a prawdopodobnie nie zwróciłby listy (ponieważ dzwoniący ma już odniesienie do tej listy; ten, w którym właśnie przeszedł).

Twój oryginalny foo, z domyślnym argumentem, nie powinien modyfikować a, czy został on wyraźnie przekazany lub otrzymał wartość domyślną. Twój kod powinien pozostawić niezmienione argumenty, chyba że jest jasne z kontekstu / nazwy / dokumentacji, którą argumenty mają być modyfikowane. Wykorzystując niezmienne wartości przekazane jako argumenty, ponieważ lokalne temporaries jest niezwyklejemnym pomysłem, niezależnie od tego, czy jesteśmy w Pythonie, czy nie i czy istnieją domyślne argumenty, ani nie.

Jeśli potrzebujesz destrukcyjnie manipulować lokalnym tymczasowym tymczasowym w trakcie obliczenia czegoś, a musisz rozpocząć manipulowanie z wartości argumentu, musisz wykonać kopię.

31
Ben 23 maj 2011, 04:24

Już zajęty temat, ale z tego, co tu czytałem, co pomogło mi zdawać sobie sprawę, jak działa wewnętrznie:

def bar(a=[]):
     print id(a)
     a = a + [1]
     print id(a)
     return a

>>> bar()
4484370232
4484524224
[1]
>>> bar()
4484370232
4484524152
[1]
>>> bar()
4484370232 # Never change, this is 'class property' of the function
4484523720 # Always a new object 
[1]
>>> id(bar.func_defaults[0])
4484370232
27
Stéphane 26 marzec 2015, 23:14

Zamierzam zademonstrować alternatywną strukturę, aby przekazać domyślną wartość listy do funkcji (działa równie dobrze ze słownikami).

Ponieważ inni szeroko komentują, parametr listy jest związany z funkcją, gdy jest zdefiniowany w przeciwieństwie do tego, kiedy zostanie wykonany. Ponieważ listy i słowniki są wyróżnione, każda zmiana do tego parametru wpłynie na inne połączenia do tej funkcji. W rezultacie kolejne połączenia z funkcją otrzymają tę udostępnioną listę, która mogła zostać zmieniona przez inne połączenia do funkcji. Co gorsza, dwa parametry używają parametru współdzielonego funkcji jednocześnie nieświadomych zmian dokonanych przez drugą.

zła metoda (prawdopodobnie ...) :

def foo(list_arg=[5]):
    return list_arg

a = foo()
a.append(6)
>>> a
[5, 6]

b = foo()
b.append(7)
# The value of 6 appended to variable 'a' is now part of the list held by 'b'.
>>> b
[5, 6, 7]  

# Although 'a' is expecting to receive 6 (the last element it appended to the list),
# it actually receives the last element appended to the shared list.
# It thus receives the value 7 previously appended by 'b'.
>>> a.pop()             
7

Możesz sprawdzić, czy są jednym i tym samym obiektem za pomocą id:

>>> id(a)
5347866528

>>> id(b)
5347866528

Per Brett Slatkin's "Python: 59 konkretnych sposobów napisania lepszego Pythona", 20: Użyj None i DocStrings, aby określić dynamiczne argumenty domyślne (str. 48)

Konwencja o osiągnięciu pożądanego wyniku w Pythonie Podaj domyślną wartość None i udokumentować rzeczywiste zachowanie w DocString.

Ta implementacja zapewnia, że każde połączenie z funkcją odbiera listę domyślną, albo lista przekazywana do funkcji.

preferowana metoda :

def foo(list_arg=None):
   """
   :param list_arg:  A list of input values. 
                     If none provided, used a list with a default value of 5.
   """
   if not list_arg:
       list_arg = [5]
   return list_arg

a = foo()
a.append(6)
>>> a
[5, 6]

b = foo()
b.append(7)
>>> b
[5, 7]

c = foo([10])
c.append(11)
>>> c
[10, 11]

Może być uzasadnione przypadki użytkowania dla "niewłaściwej metody", w której programator zamierzał domyślny parametr listy, który ma być udostępniany, ale jest to bardziej prawdopodobny wyjątek niż reguła.

19
Alexander 12 wrzesień 2015, 20:41

Rozwiązania są:

  1. Użyj None jako wartości domyślnej (lub nonce object) i włączyć to, aby utworzyć wartości w czasie wykonywania; lub
  2. Użyj a lambda jako domyślnego parametru i zadzwoń do niej bloku, aby uzyskać wartość domyślną (jest to rodzaj rzeczy, na którą jest abstrakcja Lambda).

Druga opcja jest ładna, ponieważ użytkownicy funkcji mogą przejść w kalosze, który może być już istniejący (taki jak type)

17
Marcin 30 czerwiec 2013, 16:20

Możesz to przejść, zastępując obiekt (a zatem remis w zakresie):

def foo(a=[]):
    a = list(a)
    a.append(5)
    return a

Brzydki, ale działa.

16
joedborg 15 styczeń 2013, 11:02

Kiedy to robimy:

def foo(a=[]):
    ...

... Przypisujemy argument a do listy nienazwanych , jeśli dzwoniący nie przekazuje wartości a.

Aby uzyskać prostsze dla tej dyskusji, tymczasowo podajmy nienazwaną listę nazwę. Co powiesz na pavlo?

def foo(a=pavlo):
   ...

W dowolnym momencie, jeśli dzwoniący nie powie nam, co jest a, ponownie wykorzystujemy pavlo.

Jeśli pavlo jest niewielki (modyfikowalny), a foo kończy się modyfikując, efekt, który zauważamy następny raz foo.

Więc to jest to, co widzisz (pamiętaj, {x0}} jest inicjowany do []):

 >>> foo()
 [5]

Teraz pavlo jest [5].

Dzwonienie foo() ponownie modyfikuje pavlo:

>>> foo()
[5, 5]

Określanie a Podczas wywołania {x1}} zapewnia pavlo nie jest dotknięty.

>>> ivan = [1, 2, 3, 4]
>>> foo(a=ivan)
[1, 2, 3, 4, 5]
>>> ivan
[1, 2, 3, 4, 5]

Więc pavlo jest nadal [5, 5].

>>> foo()
[5, 5, 5]
16
Saish 11 wrzesień 2014, 22:05

Czasami wykorzystywałem to zachowanie jako alternatywę dla następującego wzoru:

singleton = None

def use_singleton():
    global singleton

    if singleton is None:
        singleton = _make_singleton()

    return singleton.use_me()

Jeśli singleton jest używany tylko przez use_singleton, lubię następujący wzór jako wymiana:

# _make_singleton() is called only once when the def is executed
def use_singleton(singleton=_make_singleton()):
    return singleton.use_me()

Użyłem tego do standardowych klas klientów, które mają dostęp do zasobów zewnętrznych, a także do tworzenia dyktów lub list do zapamiętywania.

Ponieważ nie sądzę, że ten wzór jest dobrze znany, robię krótki komentarz, aby strzec przed przyszłymi nieporozumieniami.

16
bgreen-litl 6 luty 2015, 12:56

To może być prawdą, że:

  1. Ktoś korzysta z każdej funkcji języka / biblioteki i
  2. Przełączanie zachowania byłoby źle zalecane, ale

Jest całkowicie spójny do utrzymywania obu funkcji powyżej i nadal zrobienie innego punktu:

  1. Jest to pomylenie funkcji i jest niefortunna w Pythonie.

Inne odpowiedzi lub przynajmniej niektóre z nich wykonują punkty 1 i 2, ale nie 3, ani nie tworzą punktów 3 i punktów downplay 1 i 2. , ale wszystkie trzy są prawdziwe.

Może to być prawdą, że przełączanie koni w środkowej części tutaj prosiłyby o znaczną złamanie, i że może wystąpić więcej problemów, zmieniając Pythona, aby intuicyjnie obsłużyć fragment otwarcia Stefano. I może to być prawdą, że ktoś, kto znał dobrze Pythona Internal, może wyjaśnić pola polańskie konsekwencji. jednak

Istniejące zachowanie nie jest Pythonic, a Python odnosi sukces, ponieważ bardzo mało o języku narusza zasadę najmniej zaskoczenia w dowolnym miejscu w pobliżu to źle. Jest to prawdziwy problem, niezależnie od tego, czy byłoby mądrze, aby go wykorzenić. Jest to wada projektowa. Jeśli rozumiesz język znacznie lepiej, próbując śledzić zachowanie, mogę powiedzieć, że C ++ robi to wszystko i więcej; Dowiesz się wiele, nawigując na przykład, subtelne błędy wskaźnika. Ale to nie jest pytalne: ludzie, którzy dbają o pytona na tyle, by wytrwać w obliczu tego zachowania, są ludzie, którzy są narysowane językiem, ponieważ Python ma znacznie mniej niespodzianek niż inny język. Dabblers i ciekawe stają się pytonistami, gdy są zdumione, jak mało czasu zajmuje, aby uzyskać coś pracy - nie z powodu projektu fl - mam na myśli, ukrytą logiczną puzzle - to przecina intuicji programistów, którzy są rysowane do Pythona ponieważ po prostu działa .

13
Christos Hayward 16 lipiec 2009, 19:17

Ten "błąd" dał mi wiele godzin pracy! Ale zaczynam widzieć potencjalne użycie (ale chciałbym być w czasie realizacji, nadal)

Dam ci, co widzę jako przydatny przykład.

def example(errors=[]):
    # statements
    # Something went wrong
    mistake = True
    if mistake:
        tryToFixIt(errors)
        # Didn't work.. let's try again
        tryToFixItAnotherway(errors)
        # This time it worked
    return errors

def tryToFixIt(err):
    err.append('Attempt to fix it')

def tryToFixItAnotherway(err):
    err.append('Attempt to fix it by another way')

def main():
    for item in range(2):
        errors = example()
    print '\n'.join(errors)

main()

Drukuje następujące

Attempt to fix it
Attempt to fix it by another way
Attempt to fix it
Attempt to fix it by another way
9
Norfeldt 23 lipiec 2013, 10:07

Po prostu zmień funkcję:

def notastonishinganymore(a = []): 
    '''The name is just a joke :)'''
    a = a[:]
    a.append(5)
    return a
8
Prune 6 wrzesień 2018, 21:29

Myślę, że odpowiedź na to pytanie polega na tym, jak Python przekazuje dane parametru (przejść według wartości lub przez odniesienie), nieutalizację lub jak Python obsługuje instrukcję "Def".

Krótkie wprowadzenie. Po pierwsze, istnieją dwa typy danych w Pythonie, jeden jest prostym elementarnym typem danych, takich jak cyfry, a inny typ danych to obiekty. Po drugie, przechodząc dane do parametrów, Python Pass Elementary Data Typ według wartości, tj. Zrób lokalną kopię wartości do zmiennej lokalnej, ale przejść obiekt przez odniesienie, tj., Wskaźniki do obiektu.

Przyznając powyższe dwa punkty, wyjaśnijmy, co stało się z kodem Python. Dopiero z powodu przekazywania przez odniesienie do obiektów, ale nie ma nic wspólnego z niezmiennym / niezmiennym, lub prawdopodobnie fakt, że oświadczenie "def" jest wykonywane tylko raz, gdy zostanie zdefiniowany.

[] jest obiektem, więc Python przekazuje odniesienie do [] do {X0}}, tj., a jest tylko wskaźnikiem do [], który leży w pamięci jako obiekt. Jest jednak tylko jedna kopia [], jednak wiele odniesień do niego. Dla pierwszego foo (), lista [] jest zmieniona na 1 metodą dołącza. Należy jednak pamiętać, że istnieje tylko jedna kopia obiektu listy, a ten obiekt staje się teraz 1. Podczas prowadzenia drugiego foo (), jaka mówi strona wyjściowa (elementy nie są bardziej oceniane) jest błędne. a jest oceniany jako obiekt listy, chociaż teraz zawartość obiektu jest 1 . Jest to efekt przejścia przez odniesienie! Wynik foo (3) można łatwo wywodzić w ten sam sposób.

Aby dodatkowo zatwierdzić moją odpowiedź, spójrzmy na dwa dodatkowe kody.

====== Nr 2 ========

def foo(x, items=None):
    if items is None:
        items = []
    items.append(x)
    return items

foo(1)  #return [1]
foo(2)  #return [2]
foo(3)  #return [3]

[] jest obiektem, więc jest None (Ten pierwszy jest uznany, podczas gdy ten ostatni jest niezmienny. Ale nie ma nic wspólnego z pytaniem). Nikt nie jest gdzieś w przestrzeni, ale wiemy, że tam jest i jest tylko jedna kopia Brak. Więc za każdym razem, gdy wywołany jest foo, przedmioty są oceniane (w przeciwieństwie do pewnej odpowiedzi, że jest ono ocenione tylko raz), aby nie być jasne, odniesienie (lub adres) Brak. Następnie w foo, element zmienia się na [], tj., Wskazuje na inny obiekt, który ma inny adres.

====== Nr 3 =======

def foo(x, items=[]):
    items.append(x)
    return items

foo(1)    # returns [1]
foo(2,[]) # returns [2]
foo(3)    # returns [1,3]

Wywołanie foo (1) sprawia, że ​​przedmioty wskazują obiekt listy [] z adresem, powiedzmy, 11111111. Zawartość listy zmienia się na 1 w funkcji foo w dalszym ciągu, a adres jest niezmieniona, jednak 11111111. Then foo (2, []) nadchodzi. Chociaż [] w foo (2, []) ma taką samą zawartość jak domyślny parametr [] podczas dzwonienia do foo (1), ich adres jest inny! Ponieważ zapewniamy parametr wyraźnie, items musi podjąć adres tego nowego {X1}}, powiedzmy 2222222 i zwróć go po dokonaniu zmian. Teraz foo (3) jest wykonywany. Ponieważ podano tylko x elementy muszą ponownie podjąć wartość domyślną. Jaka jest wartość domyślna? Jest ustawiony przy definiowaniu funkcji FOO: obiekt listy znajdujący się w 11111111. Aby elementy są oceniane jako adres 11111111 mający element 1. Lista znajduje się w 2222222 zawiera również jeden element 2, ale nie jest to wskazywane przez elementy jeszcze. W związku z tym dołącza 3 zrobi items [1,3].

Z powyższych wyjaśnień widzimy, że Wydrobić Strona internetowa zalecana w akceptowanej odpowiedzi nie powiodła się Podaj odpowiednią odpowiedź na to pytanie. Co więcej, myślę, że punkt na stronie internetowej Effbot jest nie tak. Myślę, że kod dotyczący UI.Button jest poprawny:

for i in range(10):
    def callback():
        print "clicked button", i
    UI.Button("button %s" % i, callback)

Każdy przycisk może pomieścić wyraźną funkcję zwrotną, która wyświetli inną wartość i. Mogę podać przykład, aby pokazać to:

x=[]
for i in range(10):
    def callback():
        print(i)
    x.append(callback) 

Jeśli wykonamy x[7](), otrzymamy 7 zgodnie z oczekiwaniami, a {x1}} da 9, inną wartość i.

7
user2384994 22 sierpień 2013, 05:58

TLDR: Określenie domyślne są spójne i ściślesze ekspresyjne.


Definiowanie funkcji wpływa na dwie regulacje: Zakres definiujący zawierający Funkcja oraz zakres wykonania zawarty przez funkcję. Chociaż jest całkiem jasne, jak blokuje mapę do zakresów, pytanie jest gdzie def <name>(<args=defaults>): należy do:

...                           # defining scope
def name(parameter=default):  # ???
    ...                       # execution scope

def name część musi ocenić w zakresie definiowania - chcemy tam być dostępny name. Ocena funkcji tylko w środku sama byłaby niedostępna.

Od parameter jest stałą nazwą, możemy "ocenić" w tym samym czasie, co def name. Ma to również zaleta, wytwarza funkcję znanym podpisem jako name(parameter=...):, zamiast nagiego {x3}}.

Teraz, kiedy ocenić default?

Spójność już mówi "w definicji": wszystko inne def <name>(<args=defaults>): jest najlepiej oceniane w definicji. Opóźnianie części bytu byłby zdumiewający wybór.

Dwie opcje nie są równoważne, albo: Jeśli default jest oceniany w czasie wykonywania, nie może nie może wpływać na czas definicji. Wybór "w definicji" umożliwia wyrażanie obu przypadków, wybierając "w wykonaniu" może wyrazić tylko jeden:

def name(parameter=defined):  # set default at definition time
    ...

def name(parameter=default):     # delay default until execution time
    parameter = default if parameter is None else parameter
    ...
6
MisterMiyagi 8 sierpień 2019, 07:39

Każda inna odpowiedź wyjaśnia, dlaczego jest to w rzeczywistości ładne i pożądane zachowanie, lub dlaczego tak nie powinieneś tego potrzebować. Mój jest dla tych upartych, którzy chcą skorzystać z prawa do zginania języka do ich wola, a nie na odwrót.

Będziemy "naprawić" to zachowanie z dekoratorem, który skopiuje wartość domyślną zamiast ponownego ponownego wykorzystania tej samej instancji dla każdego argumentu pozycyjnego pozostawionego w jego wartości domyślnej.

import inspect
from copy import copy

def sanify(function):
    def wrapper(*a, **kw):
        # store the default values
        defaults = inspect.getargspec(function).defaults # for python2
        # construct a new argument list
        new_args = []
        for i, arg in enumerate(defaults):
            # allow passing positional arguments
            if i in range(len(a)):
                new_args.append(a[i])
            else:
                # copy the value
                new_args.append(copy(arg))
        return function(*new_args, **kw)
    return wrapper

Teraz przedefiniujmy naszą funkcję za pomocą tego dekoratora:

@sanify
def foo(a=[]):
    a.append(5)
    return a

foo() # '[5]'
foo() # '[5]' -- as desired

Jest to szczególnie schludne dla funkcji, które zajmują wiele argumentów. Porównać:

# the 'correct' approach
def bar(a=None, b=None, c=None):
    if a is None:
        a = []
    if b is None:
        b = []
    if c is None:
        c = []
    # finally do the actual work

Z

# the nasty decorator hack
@sanify
def bar(a=[], b=[], c=[]):
    # wow, works right out of the box!

Ważne jest, aby pamiętać, że powyższe rozwiązanie przerwy, jeśli spróbujesz użyć Args Słowo kluczowe, jak więc:

foo(a=[4])

Dekorator może być dostosowany, aby umożliwić to, ale opuścimy to jako ćwiczenie dla czytelnika;)

5
Przemek D 3 styczeń 2019, 07:38

Python: Mutable domyślny argument

Domyślne argumenty oceniają w momencie, gdy funkcja jest skompilowana do obiektu funkcyjnego. Gdy używany przez funkcję wiele razy przez tę funkcję, są one i pozostają tym samym obiektem.

Gdy są niewielkie, gdy są zmutowane (na przykład, dodając do niego elementu), pozostają zmutowane w kolejnych połączeniach.

Pozostają zmutowane, ponieważ są tym samym obiektem za każdym razem.

Równoważny kod:

Ponieważ lista jest związana z funkcją, gdy obiekt funkcyjny jest skompilowany i instancji, to:

def foo(mutable_default_argument=[]): # make a list the default argument
    """function that uses a list"""

Jest prawie dokładnie równoważny z tym:

_a_list = [] # create a list in the globals

def foo(mutable_default_argument=_a_list): # make it the default argument
    """function that uses a list"""

del _a_list # remove globals name binding

Demonstracja

Oto demonstracja - możesz sprawdzić, czy są tym samym obiektem za każdym razem, gdy są one przywoływane

  • Widząc, że lista jest tworzona przed zakończeniem funkcji kompilacji do obiektu funkcyjnego,
  • Obserwując, że identyfikator jest taki sam za każdym razem, gdy lista jest przywołana,
  • Obserwując, że lista pozostaje zmieniona, gdy funkcja używa go, nazywana jest drugi raz,
  • Obserwowanie zamówienia, w którym wyjście jest drukowane ze źródła (które dogodnie numerowane dla Ciebie):

example.py

print('1. Global scope being evaluated')

def create_list():
    '''noisily create a list for usage as a kwarg'''
    l = []
    print('3. list being created and returned, id: ' + str(id(l)))
    return l

print('2. example_function about to be compiled to an object')

def example_function(default_kwarg1=create_list()):
    print('appending "a" in default default_kwarg1')
    default_kwarg1.append("a")
    print('list with id: ' + str(id(default_kwarg1)) + 
          ' - is now: ' + repr(default_kwarg1))

print('4. example_function compiled: ' + repr(example_function))


if __name__ == '__main__':
    print('5. calling example_function twice!:')
    example_function()
    example_function()

I bieganie z python example.py:

1. Global scope being evaluated
2. example_function about to be compiled to an object
3. list being created and returned, id: 140502758808032
4. example_function compiled: <function example_function at 0x7fc9590905f0>
5. calling example_function twice!:
appending "a" in default default_kwarg1
list with id: 140502758808032 - is now: ['a']
appending "a" in default default_kwarg1
list with id: 140502758808032 - is now: ['a', 'a']

Czy to narusza zasadę "najmniej zdumienia"?

To kolejność egzekucji jest często mylącym nowym użytkownikom Pythona. Jeśli rozumiesz model wykonania Pythona, staje się to dość oczekiwana.

Zwykłe instrukcje dla nowych użytkowników Pythona:

Ale dlatego zwykłe instrukcje dla nowych użytkowników jest tworzenie ich domyślnych argumentów takich jak ten:

def example_function_2(default_kwarg=None):
    if default_kwarg is None:
        default_kwarg = []

Wykorzystuje to brak Singletona jako obiektu Sentinel, aby opowiedzieć funkcję, niezależnie od tego, czy otrzymaliśmy argument inny niż domyślny. Jeśli nie otrzymamy argumentu, właściwie chcemy użyć nowej pustej listy, jako domyślne domyślne [].

Jako Sekcja samouczka na przepływie sterowania mówi:

Jeśli nie chcesz, aby domyślnie udostępnić między kolejnymi połączeniami, możesz napisać taką funkcję, zamiast tego:

def f(a, L=None):
    if L is None:
        L = []
    L.append(a)
    return L
25
Aaron Hall 23 grudzień 2017, 21:18

Najkrótszą odpowiedź będzie prawdopodobnie "definicja jest realizacją", dlatego cały argument nie ma surowego znaczenia. Jako bardziej wymyślony przykład możesz cytować to:

def a(): return []

def b(x=a()):
    print x

Mam nadzieję, że wystarczy pokazać, że nie wykonywanie wyrażeń domyślnych argumentów w czasie wykonywania def stwierdzenie nie jest łatwe lub nie ma sensu lub obu.

Zgadzam się, że jest to gotcha, gdy próbujesz użyć domyślnych konstruktorów.

24
Ashraf.Shk786 20 maj 2018, 23:22

Prosty obejście za pomocą Brak

>>> def bar(b, data=None):
...     data = data or []
...     data.append(b)
...     return data
... 
>>> bar(3)
[3]
>>> bar(3)
[3]
>>> bar(3)
[3]
>>> bar(3, [34])
[34, 3]
>>> bar(3, [34])
[34, 3]
20
hugo24 28 luty 2013, 11:10