"Najmniejsze zdziwienie" i zmienny Argument domyślny

Każdy, kto wystarczająco długo majstrował z pytonem, został ugryziony (lub rozerwany na kawałki) przez następujący problem:

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

Nowicjusze Pythona oczekują, że ta funkcja zawsze zwróci listę zawierającą tylko jeden element: [5]. Wynik jest zupełnie inny i bardzo zadziwiający (dla nowicjusza): {]}

>>> foo()
[5]
>>> foo()
[5, 5]
>>> foo()
[5, 5, 5]
>>> foo()
[5, 5, 5, 5]
>>> foo()
Jeden z moich menedżerów zetknął się kiedyś z tą funkcją i nazwał ją" dramatyczną wadą projektu " języka. Odpowiedziałem, że zachowanie ma podstawowe Wyjaśnienie, i to jest naprawdę bardzo zastanawiające i nieoczekiwane, jeśli nie rozumiesz 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ę, by doświadczane zachowanie miało praktyczne zastosowanie (kto naprawdę używał zmiennych statycznych w C, bez błędów hodowlanych?)

Edit :

Baczek dał ciekawy przykład. Wraz z większością Waszych komentarzy i Utaal jest w szczególności, 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 względna, gdzie umieścić zakres parametrów: wewnątrz funkcji lub "razem" z nią?

Wykonanie wiązania wewnątrz funkcji oznaczałoby, że x jest faktycznie związane z podaną wartością domyślną, gdy funkcja jest wywołana, a nie zdefiniowana, czymś, co przedstawiłoby głęboką wadę: linia def byłaby "hybrydowa" w tym sensie, że część wiązania (obiektu funkcji) stanie się przy definicji, a część (przypisanie domyślnych parametrów) w czasie wywołania funkcji.

Rzeczywiste zachowanie jest bardziej spójne: wszystko z tej linii jest oceniane, gdy ta linia jest wykonywana, co oznacza, że w definicji funkcji.

Author: Matt, 2009-07-15

30 answers

Właściwie to nie jest wada projektowa, ani to nie z powodu wewnętrznych, ani wydajności.
Wynika to po prostu z faktu, że funkcje w Pythonie są obiektami pierwszej klasy, a nie tylko fragmentem kodu.

Jak tylko zaczniesz myśleć w ten sposób, to całkowicie ma to sens: funkcja jest obiektem ocenianym na podstawie jego definicji; domyślne parametry są rodzajem "danych członkowskich" i dlatego ich stan może się zmieniać z jednego wywołania do drugiego - dokładnie tak, jak w każdym innym obiekt.

W każdym razie, Effbot ma bardzo ładne wyjaśnienie przyczyn takiego zachowania w domyślnych wartościach parametrów w Pythonie .
Uważam, że jest to bardzo jasne i naprawdę sugeruję przeczytanie go dla lepszej wiedzy o tym, jak działają obiekty funkcyjne.

 1386
Author: rob,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-17 21:29:39

Załóżmy, że masz następujący kod

fruits = ("apples", "bananas", "loganberries")

def eat(food=fruits):
    ...

Kiedy widzę deklarację jedzenia, najmniej zadziwiające jest myślenie, że jeśli pierwszy parametr nie zostanie podany, to będzie równy krotce ("apples", "bananas", "loganberries")

Jednak, przypuszczalnie później w kodzie, robię coś w rodzaju

def some_random_function():
    global fruits
    fruits = ("blueberries", "mangos")

Wtedy, gdyby domyślne parametry były związane podczas wykonywania funkcji, a nie deklaracji funkcji, to byłbym zdumiony (w bardzo zły sposób) odkryciem, że owoce zostały zmienione. To byłoby bardziej zdumiewające IMO niż odkrycie, że Twoja foo funkcja powyżej mutuje listę.

Prawdziwy problem tkwi w zmiennych zmiennych, i wszystkie języki mają ten problem do pewnego stopnia. Mam pytanie: Załóżmy, że w Javie mam następujący kod:

StringBuffer s = new StringBuffer("Hello World!");
Map<StringBuffer,Integer> counts = new HashMap<StringBuffer,Integer>();
counts.put(s, 5);
s.append("!!!!");
System.out.println( counts.get(s) );  // does this work?

Teraz, czy moja Mapa używa wartości klucza StringBuffer, gdy została umieszczona na mapie, czy przechowuje klucz przez odniesienie? Tak czy inaczej, ktoś jest zdumiony; albo osoba, która próbowała wyciągnąć przedmiot z Map używając wartości identycznej do tej, z którą ją umieścili, lub osoby, która nie może odzyskać swojego obiektu, mimo że klucz, którego używają, jest dosłownie tym samym obiektem, który został użyty do umieszczenia go na mapie (To właśnie dlatego Python nie pozwala na używanie jego mutowalnych wbudowanych typów danych jako kluczy słownikowych).

Twój przykład jest dobrym przykładem, w którym przybysze Pythona będą zaskoczeni i ugryzieni. Ale argumentowałbym, że gdybyśmy to "naprawili", to stworzyłoby to tylko inna sytuacja, w której zamiast tego byliby pogryzieni, a ta byłaby jeszcze mniej intuicyjna. Co więcej, zawsze tak jest w przypadku zmiennych zmiennych; zawsze napotykasz przypadki, w których ktoś może intuicyjnie oczekiwać jednego lub przeciwnego zachowania w zależności od tego, jaki kod pisze.

Osobiście lubię obecne podejście Pythona: domyślne argumenty funkcji są obliczane, gdy funkcja jest zdefiniowana, a ten obiekt jest zawsze domyślny. Przypuszczam, że mogą użycie pustej listy, ale taka specjalna obudowa spowodowałaby jeszcze większe zdziwienie, nie wspominając o wstecznej niezgodności.

 238
Author: Eli Courtwright,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-27 14:24:50

AFAICS nikt jeszcze nie opublikował odpowiedniej części dokumentacji :

Domyślne wartości parametrów są obliczane podczas wykonywania definicji funkcji.{[7] } oznacza to, że wyrażenie jest obliczane raz, gdy funkcja jest zdefiniowana, i że ta sama" wstępnie obliczona " wartość jest używana dla każdego wywołania. Jest to szczególnie ważne, aby zrozumieć, kiedy parametr domyślny jest zmiennym obiektem, takim jak lista lub słownik: jeśli funkcja modyfikuje obiekt (np. przez dodanie elementu do listy), wartość domyślna jest w efekcie modyfikowana. Zasadniczo nie jest to zamierzone. Sposobem na obejście tego problemu jest użycie None jako domyślnego i jawne przetestowanie go w ciele funkcji [...]

 201
Author: glglgl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-10 14:50:42

Nic nie wiem o wewnętrznym funkcjonowaniu interpretera Pythona (i nie jestem ekspertem w kompilatorach i interpretatorach), więc nie wiń mnie, jeśli proponuję coś niemożliwego lub niemożliwego.

Pod warunkiem, że obiekty Pythona są mutowalne myślę, że powinno to być brane pod uwagę przy projektowaniu domyślnych argumentów. Kiedy tworzysz listę:

a = []

Spodziewasz się, że otrzymasz nową listę wskazaną przez a .

Dlaczego a = [] w

def x(a=[]):

Utworzyć nową listę na definicji funkcji, a nie na wywołaniu? To tak, jakbyś pytał: "jeśli użytkownik nie poda argumentu, to Utwórz instancję nowej listy i użyj jej tak, jakby została wyprodukowana przez wywołującego". Myślę, że jest to niejednoznaczne:

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

Użytkownik, czy chcesz, aby a domyślnie ustawiała datetime odpowiadającą, gdy definiujesz lub wykonujesz x ? W tym przypadku, podobnie jak w poprzednim, zachowam takie samo zachowanie jak jeśli domyślnym argumentem "assignment" była pierwsza instrukcja funkcji (datetime.now () called on function invocation). Z drugiej strony, jeśli użytkownik chciał odwzorowania definicji w czasie, mógł napisać:

b = datetime.datetime.now()
def x(a=b):
Wiem, Wiem: to koniec. Alternatywnie Python może dostarczyć słowo kluczowe wymuszające definition-time binding:
def x(static a=b):
 100
Author: Utaal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-05 07:33:47

Cóż, powodem jest po prostu to, że wiązania są wykonywane, gdy kod jest wykonywany, a definicja funkcji jest wykonywana, dobrze... gdy funkcje są zdefiniowane.

Porównaj to:

class BananaBunch:
    bananas = []

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

Ten kod cierpi dokładnie na ten sam nieoczekiwany przypadek. bananas jest atrybutem klasy, a zatem, gdy dodajesz do niego rzeczy, jest on dodawany do wszystkich instancji tej klasy. Powód jest dokładnie taki sam.

To po prostu "Jak to działa" , i sprawia, że działa inaczej w funkcji przypadek byłby prawdopodobnie skomplikowany, a w przypadku klasy prawdopodobnie niemożliwy, lub przynajmniej spowalniałby instancję obiektu, ponieważ musielibyśmy trzymać Kod klasy wokół i wykonywać go podczas tworzenia obiektów.

Tak, to nieoczekiwane. Ale gdy grosz spadnie, idealnie pasuje do tego, jak działa Python w ogóle. W rzeczywistości jest to dobra pomoc dydaktyczna, a kiedy zrozumiesz, dlaczego tak się dzieje, będziesz grok python znacznie lepiej.

Że powinno się wyróżniać w każdym dobry samouczek Pythona. Ponieważ, jak wspomniałeś, każdy prędzej czy później napotyka ten problem.

 75
Author: Lennart Regebro,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-19 22:53:35

Kiedyś myślałem, że tworzenie obiektów w czasie wykonywania byłoby lepszym podejściem. Teraz jestem mniej pewny, ponieważ tracisz kilka przydatnych funkcji, chociaż może być warto, aby zapobiec zamieszaniu nowicjuszy. Wady tego rozwiązania to:

1. Wydajność

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

Jeśli używana jest ocena czasu wywołania, to kosztowna funkcja jest wywoływana za każdym razem, gdy funkcja jest używana bez argumentu. Albo zapłacisz wysoką cenę za każde połączenie, albo trzeba ręcznie buforować wartość zewnętrznie, zanieczyszczając przestrzeń nazw i dodając szczegółowość.

2. Wymuszanie parametrów związanych

Użyteczną sztuczką jest powiązanie parametrów lambda z bieżącym wiązaniem zmiennej podczas tworzenia lambda. Na przykład:

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

Zwraca listę funkcji, które zwracają 0,1,2,3... odpowiednio. Jeśli zachowanie zostanie zmienione, zamiast tego będą one wiązać i do czasu wywołania wartości i, więc Pobierz listę funkcji, które wszystkie zwróciły 9.

Jedynym sposobem na zaimplementowanie tego w przeciwnym razie byłoby utworzenie kolejnego zamknięcia Z i bound, czyli:

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

3. Introspekcja

Rozważmy 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óry

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

Ta informacja jest bardzo przydatna przy generowaniu dokumentów, metaprogramowaniu, dekoratorach itp.

Przypuśćmy, że zachowanie domyślnych wartości można zmienić tak, że jest to odpowiednik:

_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=[]

Straciliśmy jednak możliwość introspekcji i sprawdzenia, jakie są domyślne argumenty . Ponieważ obiekty nie zostały zbudowane, nie możemy ich zdobyć bez wywołania funkcji. Najlepsze, co możemy zrobić, to zapisać kod źródłowy i zwrócić go jako ciąg znaków.

 53
Author: Brian,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-16 19:13:35

5 punktów w obronie Pythona

  1. Prostota : zachowanie jest proste w następującym sensie: Większość ludzi wpada w tę pułapkę tylko raz, a nie kilka razy.

  2. Spójność : Python zawsze przekazuje obiekty, a nie nazwy. Domyślnym parametrem jest oczywiście część funkcji nagłówek (nie Ciało funkcji). Należy zatem ocenić w czasie ładowania modułu (i tylko w czasie ładowania modułu, chyba że zagnieżdżone), nie w funkcji czas na telefon.

  3. Użyteczność: jak podkreśla Frederik Lundh w swoim wyjaśnieniu z "wartości domyślnych parametrów w Pythonie" , obecne zachowanie może być bardzo przydatne dla zaawansowanego programowania. (Używaj oszczędnie.)

  4. Wystarczająca dokumentacja: w najbardziej podstawowej dokumentacji Pythona, tutorial, problem jest głośno ogłaszany jako "Ważne Ostrzeżenie" w pierwszej sekcji " więcej o definiowaniu Funkcje " . Ostrzeżenie nawet używa boldface, który jest rzadko stosowany poza pozycjami. RTFM: przeczytaj dobrą instrukcję.

  5. Meta-learning : wpadnięcie w pułapkę jest w rzeczywistości bardzo pomocny moment (przynajmniej jeśli jesteś refleksyjnym uczniem), ponieważ później lepiej zrozumiesz sedno "Konsekwencja" powyżej i to będzie nauczę cię wielu rzeczy o Pythonie.

 50
Author: Lutz Prechelt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-26 08:33:17

Dlaczego nie zaintrospektujesz?

Jestem naprawdę zaskoczony, że nikt nie dokonał wnikliwej introspekcji oferowanej przez Pythona (2 i 3) na telefonach.

Dana prosta mała funkcjafunc zdefiniowana jako:

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

Gdy Python ją napotka, pierwszą rzeczą, jaką zrobi, to skompiluje ją, aby utworzyć obiekt code dla tej funkcji. Gdy ten krok kompilacji jest wykonywany, Python ocenia *, a następnie przechowuje domyślne argumenty (pusta lista [] tutaj) w samym obiekcie function . Jak wspomniano powyżej: lista a Może być teraz uważana za członek funkcji func.

Zróbmy więc introspekcję, przed i po, aby sprawdzić, jak lista zostanie rozszerzona wewnątrz obiektu funkcji. Używam Python 3.x do tego, w Pythonie 2 stosuje się to samo (użyj __defaults__ lub func_defaults w Pythonie 2; Tak, dwie nazwy dla tej samej rzeczy).

Funkcja Przed Wykonanie:

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

Po wykonaniu tej definicji Python pobierze wszystkie domyślne parametry określone (a = []tutaj) i zapakuje je do atrybutu __defaults__ dla obiektu function (odpowiednia sekcja: Callables):

>>> func.__defaults__
([],)

O. K, więc pusta lista jako pojedynczy wpis w __defaults__, zgodnie z oczekiwaniami.

Funkcja Po Wykonaniu:

Wykonajmy teraz tę funkcję:

>>> func()

Teraz, zobaczmy te __defaults__ Jeszcze raz:

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

zdumiony? wartość wewnątrz obiektu zmienia się! Kolejne wywołania funkcji będą teraz po prostu dołączane do osadzonego obiektu list:

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

Więc, masz to, powodem, dla którego ta 'wada' ma miejsce, jest to, że domyślne argumenty są częścią obiektu function. Nie ma tu nic dziwnego, to wszystko jest trochę zaskakujące.

Powszechnym rozwiązaniem do walki z tym jest użycie None jako domyślnej, a następnie zainicjowanie w ciało funkcji:

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

Ponieważ ciało funkcji jest wykonywane za każdym razem na nowo, zawsze otrzymujesz nową nową pustą listę, jeśli żaden argument nie został przekazany dla a.


Aby jeszcze bardziej zweryfikować, czy lista w __defaults__ jest taka sama jak ta użyta w funkcji func, możesz po prostu zmienić swoją funkcję, aby zwrócić id z listy a użytej wewnątrz ciała funkcji. Następnie porównaj go z listą w __defaults__ (pozycja [0] w __defaults__), a zobaczysz, jak naprawdę odnoszą się one do ta sama instancja listy:

>>> def func(a = []): 
...     a.append(5)
...     return id(a)
>>>
>>> id(func.__defaults__[0]) == func()
True
Wszystko z mocą introspekcji!

* aby sprawdzić, czy Python oblicza domyślne argumenty podczas kompilacji funkcji, spróbuj wykonać następujące polecenie:

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

Jak zauważysz, input() jest wywoływana przed rozpoczęciem procesu budowania funkcji i wiązania jej z nazwą bar.

 47
Author: Jim Fasarakis Hilliard,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-10-11 16:33:49

To zachowanie jest łatwo wyjaśnione przez:

  1. function (class etc.) deklaracja 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żde wywołanie przypisania tworzy nowy obiekt int-wyświetlany jest nowy obiekt
  2. b nie zmienia się - nowa tablica jest budowana z domyślnej wartości i drukowana
  3. c zmiany-operacja jest wykonywana na tym samym obiekcie - i jest drukowane
 42
Author: ymv,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-24 06:34:34

Pytasz dlaczego to:

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

Nie jest wewnętrznie równoznaczne z tym:

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 jawnego wywołania func( None, None), które zignorujemy.

Innymi słowy, zamiast Oceniać domyślne parametry, dlaczego nie zapisać każdego z nich i ocenić je podczas wywoływania funkcji?

Jedna odpowiedź jest prawdopodobnie tam--to skutecznie przekształci każdą funkcję z domyślnymi parametrami w Zamknięcie. Nawet jeśli wszystko jest ukryte w interpreter, a nie pełne zamknięcie, DANE muszą być gdzieś przechowywane. Byłoby wolniej i zużywałoby więcej pamięci.

 32
Author: Glenn Maynard,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-15 20:18:14

1) tzw. problem "zmiennego argumentu domyślnego" jest ogólnie specjalnym przykładem wykazującym, że:
"Wszystkie funkcje z tym problemem cierpią również z powodu podobnego problemu efektu ubocznego na rzeczywistym parametrze,"
Jest to sprzeczne z zasadami programowania funkcjonalnego, Zwykle niezauważalne i powinno być ustalone 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: a Kopia
Absolutnie bezpiecznym rozwiązaniem jest copy lub deepcopy najpierw obiekt input, a następnie zrobić cokolwiek 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 typów mutowalnych ma metodę kopiowania jak some_dict.copy() lub some_set.copy() lub może być łatwo kopiowane jak somelist[:] lub list(some_list). Każdy obiekt może być również skopiowany przez copy.copy(any_object) lub dokładniej przez copy.deepcopy() (Ten ostatni jest przydatny, jeśli obiekt zmienny składa się z obiektów zmiennych). Niektóre obiekty są zasadniczo oparte na efektach ubocznych, takich jak obiekt "plik" i nie mogą być w znaczący sposób odtworzone przez kopię. kopiowanie

Przykładowy problem dla podobne 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 powinien być zapisywany w żadnym atrybucie public instancji zwracanej przez tę funkcję. (Zakładając, że prywatne atrybuty instancji nie powinny być modyfikowane poza tą klasą lub podklasami przez Konwencję. tzn. _var1 jest atrybutem prywatnym)

Wniosek:
Obiekty parametrów wejściowych nie powinny być modyfikowane w miejscu (zmutowane) ani nie powinien być powiązany z obiektem zwracanym przez funkcję. (Jeśli preferujemy programowanie bez skutków ubocznych, co jest zdecydowanie zalecane. zobacz Wiki o "efekcie ubocznym" (pierwsze dwa akapity są odpowiednie w tym kontekście.) .)

2)
Tylko jeśli efekt uboczny dla rzeczywistego parametru jest wymagany, ale niepożądany dla domyślnego parametru, to użytecznym rozwiązaniem jest def ...(var1=None): if var1 is None: var1 = [] więcej..

3) w niektórych przypadkach jest zmiennym zachowaniem domyślne parametry użyteczne .

 31
Author: hynekcer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:47:32

W rzeczywistości nie ma to nic wspólnego z wartościami domyślnymi, poza tym, że często pojawia się jako nieoczekiwane zachowanie, gdy piszesz funkcje z zmiennymi 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 domyślnych wartości w tym kodzie, ale masz dokładnie ten sam problem.

Problem polega na tym, że foo jest modyfikacją zmienną zmienną przekazywalną od wywołującego, gdy wywołujący tego nie oczekuje. Taki kod byłby w porządku, gdyby funkcja była wywołana czymś w stylu append_5; następnie wywołujący wywołałby funkcję w celu modyfikacji przekazywanej przez nią wartości, a zachowanie byłoby oczekiwane. Ale taka funkcja raczej nie pobierze domyślnego argumentu i prawdopodobnie nie zwróci listy (ponieważ wywołujący ma już odniesienie do tej listy; tej, którą właśnie przekazał).

Twój oryginalny foo, z domyślnym argumentem, nie powinien modyfikować a czy został jawnie przekazany lub otrzymał wartość domyślną. Twój kod powinien być zmienny same argumenty, chyba że z kontekstu/nazwy/dokumentacji wynika, że argumenty powinny zostać zmodyfikowane. Używanie zmiennych wartości przekazywanych jako argumenty jako lokalne tymczasowe jest bardzo złym pomysłem, niezależnie od tego, czy jesteśmy w Pythonie, czy nie i czy istnieją argumenty domyślne, czy nie.

Jeśli musisz destrukcyjnie manipulować lokalnym tymczasowym w trakcie przetwarzania czegoś i musisz rozpocząć manipulację od wartości argumentu, musisz zrobić przyjąłem.

 26
Author: Ben,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-05-23 04:24:30

To optymalizacja wydajności. W wyniku tej funkcjonalności, które z tych dwóch wywołań funkcji myślisz, że jest szybsze?

def print_tuple(some_tuple=(1,2,3)):
    print some_tuple

print_tuple()        #1
print_tuple((1,2,3)) #2
Podpowiem Ci. Oto demontaż (zobacz http://docs.python.org/library/dis.html):

#1

0 LOAD_GLOBAL              0 (print_tuple)
3 CALL_FUNCTION            0
6 POP_TOP
7 LOAD_CONST               0 (None)
10 RETURN_VALUE

#2

 0 LOAD_GLOBAL              0 (print_tuple)
 3 LOAD_CONST               4 ((1, 2, 3))
 6 CALL_FUNCTION            1
 9 POP_TOP
10 LOAD_CONST               0 (None)
13 RETURN_VALUE

Wątpię, aby doświadczane zachowanie miało praktyczne zastosowanie (kto naprawdę używał zmiennych statycznych w C, bez błędów hodowlanych ?)

Jak widać, tam is a performance benefit when using immutable default arguments. Może to mieć znaczenie, jeśli jest to często wywoływana funkcja, lub jeśli konstruowanie domyślnego argumentu zajmuje dużo czasu. Pamiętaj również, że Python nie jest C. W C masz stałe, które są prawie wolne. W Pythonie nie masz tej korzyści.

 25
Author: Jason Baker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-02 21:52:30

Już zajęty temat, ale z tego, co tu przeczytałem, poniższe pomogło mi zrozumieć, jak to 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
 23
Author: Stéphane,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-03-26 23:14:01

Python: Zmienny Argument Domyślny

Argumenty domyślne są obliczane podczas kompilacji funkcji do obiektu funkcji. Gdy funkcja jest używana wielokrotnie przez tę funkcję, są one i pozostają tym samym obiektem.

Kiedy są mutowalne, kiedy mutowane (na przykład przez dodanie do niego elementu) pozostają mutowane podczas kolejnych wywołań.

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

Odpowiednik kod:

Ponieważ lista jest powiązana z funkcją, gdy obiekt function jest kompilowany i tworzy instancję, to:

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

Jest prawie dokładnie równoznaczne 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ą one tym samym obiektem za każdym razem, gdy odwołują się do nich

  • widząc, że lista jest tworzona przed zakończeniem kompilacji funkcji do obiektu funkcji,
  • obserwując, że id jest takie same dla każdego czas odnoszenia się do listy,
  • obserwując, że lista pozostaje zmieniona, gdy funkcja, która jej używa, zostanie wywołana po raz drugi,
  • obserwując kolejność, w jakiej wyjście jest drukowane ze źródła (które wygodnie dla Ciebie ponumerowałem):

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 uruchamianie go 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ę "najmniejszego zdziwienia"?

Ta kolejność wykonania jest często myląca dla nowych Użytkowników Pythona. Jeśli zrozum Model wykonania Pythona, wtedy stanie się całkiem oczekiwany.

Zwykła instrukcja dla nowych Użytkowników Pythona:

Ale właśnie dlatego zwyczajową instrukcją dla nowych użytkowników jest tworzenie domyślnych argumentów w ten sposób:

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

Używa singletonu None jako obiektu sentinel, aby powiedzieć funkcji, czy otrzymaliśmy argument inny niż domyślny. Jeśli nie otrzymamy argumentu, to chcemy użyć nowej pustej listy [], jako default.

Jak sekcja tutorial o przepływie sterowania mówi:

Jeśli nie chcesz, aby wartość domyślna była współdzielona pomiędzy kolejnymi połączeniami, możesz zamiast tego napisać funkcję w ten sposób:

def f(a, L=None):
    if L is None:
        L = []
    L.append(a)
    return L
 19
Author: Aaron Hall,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-23 21:18:35

Proste obejście przy użyciu None

>>> 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]
 18
Author: hugo24,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-02-28 11:10:16

To zachowanie nie jest zaskakujące, jeśli wziąć pod uwagę:]}

    Zachowanie atrybutów klasy tylko do odczytu przy próbie przypisania, i że]}
  1. funkcje są obiektami (dobrze wyjaśnione w zaakceptowanej odpowiedzi).

Rola (2) został szeroko omówiony w tym wątku. (1) jest prawdopodobnie czynnikiem powodującym zdziwienie, ponieważ takie zachowanie nie jest "intuicyjne", gdy pochodzi z innych języki.

(1) jest opisany w samouczku Pythona na klasach . Przy próbie przypisania wartości do atrybutu klasy tylko do odczytu:

...wszystkie zmienne znajdujące się poza zakresem wewnętrznym to tylko do odczytu(próba zapisu do takiej zmiennej spowoduje po prostu utworzenie Nowa zmienna lokalna w najbardziej wewnętrznym zakresie, pozostawiając identyczną nazwa zmiennej zewnętrznej bez zmian).

Wróć do oryginalnego przykładu i rozważ powyższe punkty:

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

Tutaj foo jest obiektem, a {[3] } atrybutem foo (dostępnym w foo.func_defs[0]). Ponieważ a jest listą, a jest zmienna i dlatego jest atrybutem do odczytu i zapisu foo. Jest inicjalizowana do pustej listy określonej przez podpis podczas tworzenia instancji funkcji i jest dostępna do odczytu i zapisu tak długo, jak długo istnieje obiekt function.

Wywołanie foo bez nadpisywania wartości domyślnej używa tej wartości z foo.func_defs. W w tym przypadku {[5] } jest używane dla a w zakresie kodu obiektu funkcji. Zmiany na a zmiana foo.func_defs[0], która jest częścią obiektu foo i trwa pomiędzy wykonaniem kodu w foo.

Teraz porównaj to z przykładem z dokumentacji emulując domyślne zachowanie argumentów innych języków , tak że domyślne wartości podpisu funkcji są używane za każdym razem, gdy funkcja jest wykonywana:

def foo(a, L=None):
    if L is None:
        L = []
    L.append(a)
    return L

Biorąc (1) oraz (2) na konto, można zobaczyć, dlaczego osiąga to pożądane zachowanie:

  • gdy obiekt funkcji foo jest utworzony, foo.func_defs[0] jest ustawiony na None, obiekt niezmienny.
  • gdy funkcja jest wykonywana z wartościami domyślnymi (bez parametru określonego dla L w wywołaniu funkcji), foo.func_defs[0] (None) jest dostępny w zakresie lokalnym jako L.
  • na L = [], przypisanie nie może się udać w foo.func_defs[0], ponieważ atrybut ten jest tylko do odczytu.
  • Per (1), Nowa zmienna lokalna o nazwie L jest tworzona w lokalnym zakresie i używane do pozostałej części wywołania funkcji. foo.func_defs[0] pozostaje więc bez zmian dla przyszłych inwokacji foo.
 18
Author: Dmitry Minkovsky,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-01 15:30:56

Najkrótsza odpowiedź brzmiałaby prawdopodobnie "definicja jest wykonaniem", dlatego cały argument nie ma ścisłego sensu. Jako bardziej wymyślny przykład możesz przytoczyć:

def a(): return []

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

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

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

 18
Author: Baczek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-20 23:22:19

Oto rozwiązania:

  1. Użyj None jako domyślnej wartości (lub nonce object) i włącz ją, aby utworzyć wartości w czasie wykonywania; lub
  2. Użyj lambda jako domyślnego parametru i wywołaj go w bloku try, aby uzyskać domyślną wartość (jest to coś, do czego służy abstrakcja lambda).

Druga opcja jest miła, ponieważ użytkownicy funkcji mogą przekazać wywołanie, które może być już istniejące (np. type)

 17
Author: Marcin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-30 16:20:35

Czasami wykorzystuję 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żywane tylko przez use_singleton, podoba mi się następujący wzór jako zamiennik:

# _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 tworzenia instancji klas klienckich, które mają dostęp do zewnętrznych zasobów, a także do tworzenia dictów lub list do memoizacji.

Ponieważ nie wydaje mi się, aby ten wzór był dobrze znany, zamieszczam krótki komentarz, aby uchronić się przed przyszłymi nieporozumieniami.

 16
Author: bgreen-litl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-06 12:56:53

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

Jak już inni komentowali, parametr list jest związany z funkcją, gdy jest zdefiniowana, a nie kiedy jest wykonywana. Ponieważ listy i słowniki są zmienne, każda zmiana tego parametru wpłynie na inne wywołania tej funkcji. W rezultacie kolejne wywołania funkcji otrzymają tę udostępnioną listę, która mogły zostać zmienione przez inne wywołania funkcji. Co gorsza, dwa parametry korzystają z dzielonego parametru tej funkcji jednocześnie, nie zważając na zmiany dokonane przez inne.

Niewłaściwa 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ą one jednym i tym samym obiektem, używając id:

>>> id(a)
5347866528

>>> id(b)
5347866528
[[6]} według Bretta Slatkina "Effective Python: 59 Specific Ways to Write Better Python", pozycja 20: Użyj None i Docstrings, aby określić dynamiczne domyślne argumenty (str. 48)

Konwencja osiągnięcia pożądanego rezultatu w Pythonie jest podaj domyślną wartość None i udokumentuj rzeczywiste zachowanie w docstringu.

Ta implementacja zapewnia, że każde wywołanie funkcji otrzymuje listę domyślną lub listę przekazaną 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]

Mogą istnieć uzasadnione przypadki użycia "niewłaściwej metody", w których programista zamierzał domyślny parametr listy, który ma być współdzielony, ale jest to bardziej prawdopodobne wyjątek niż reguła.

 16
Author: Alexander,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-12 20:41:53

Można to obejść, zastępując obiekt (a więc remis z zakresem):

def foo(a=[]):
    a = list(a)
    a.append(5)
    return a
Brzydkie, ale działa.
 15
Author: jdborg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-15 11:02:03

Kiedy to zrobimy:

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

... przypisujemy argument a do listy bez nazwy , jeśli wywołujący nie przekazuje wartości a.

Aby uprościć tę dyskusję, tymczasowo nadajmy nienazwanej liście nazwę. A może pavlo ?

def foo(a=pavlo):
   ...

W każdej chwili, jeśli rozmówca nie powie nam, czym jest a, używamy ponownie pavlo.

Jeśli pavlo jest zmienny (modyfikowalny), a foo kończy się jego modyfikacją, efekt zauważamy następnym razem foo jest wywołane bez podania a.

Więc to jest to, co widzisz (pamiętaj, pavlo jest inicjalizowane na []):

 >>> foo()
 [5]

Teraz, pavlo jest [5].

Wywołanie foo() ponownie modyfikuje pavlo ponownie:

>>> foo()
[5, 5]

Podanie a podczas wywołania foo() zapewnia, że pavlo nie zostanie 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]
 14
Author: Saish,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-11 22:05:43

Może być prawdą, że:

  1. ktoś używa każdej funkcji języka/biblioteki i
  2. Zmiana zachowania tutaj byłaby nierozważna, ale]}

Jest to całkowicie zgodne, aby trzymać się obu powyższych cech i jeszcze jeden punkt:

  1. jest to funkcja myląca i niefortunna w Pythonie.

Inne odpowiedzi, a przynajmniej niektóre z nich albo piszą punkty 1 i 2, ale nie 3, albo piszą punkt 3 i bagatelizują punkty 1 i 2. Ale wszystkie trzy są prawdziwe.

Może być prawdą, że zamiana koni w środku strumienia będzie wymagała znacznego złamania i że może być więcej problemów powstałych przez zmianę Pythona na intuicyjnie obsługiwany fragment otwarcia. I może być prawdą, że ktoś, kto dobrze znał język Python, mógłby wyjaśnić konsekwencje.

Istniejące zachowanie nie jest Pythoniczne, a Python jest udany, ponieważ bardzo mało o języku łamie zasadę najmniejszego zdziwienia gdziekolwiek blisko tak źle. Jest to prawdziwy problem, niezależnie od tego, czy mądrze byłoby go wykorzenić. Jest to wada konstrukcyjna. Jeśli lepiej rozumiesz język, próbując prześledzić jego zachowanie, mogę powiedzieć, że C++ robi to wszystko i więcej; dużo się uczysz, poruszając się na przykład subtelnymi błędami wskaźników. Ale to nie jest Pythonic: ludzie, którym zależy na Pythonie na tyle, aby wytrwać w obliczu tego zachowania, to ludzie, którzy są przyciągani do język, ponieważ Python ma znacznie mniej niespodzianek niż inny język. Dabblerzy i ciekawscy stają się Pythonistami, gdy są zdumieni, jak mało czasu zajmuje, aby coś działało-nie z powodu design FL-mam na myśli, ukrytej logiki-która przecina intuicje programistów, którzy są przyciągani do Pythona, ponieważ {15]} po prostu działa {16]}.

 11
Author: JonathanHayward,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-16 19:17:59

Ten "robak" dał mi dużo nadgodzin! Ale zaczynam dostrzegać potencjalne wykorzystanie tego (ale chciałbym, aby to było w czasie realizacji, nadal)

Dam ci to, co widzę jako użyteczny 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
Author: Norfeldt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-23 10:07:58

Myślę, że odpowiedź na to pytanie leży w tym, jak python przekazuje dane do parametru (przekazuje przez wartość lub przez odniesienie), a nie zmienność lub jak python radzi sobie z instrukcją "def".

Krótkie wprowadzenie. Po pierwsze, istnieją dwa typy danych w Pythonie, jeden to prosty elementarny typ danych, taki jak liczby, a drugi typ danych to obiekty. Po drugie, podczas przekazywania danych do parametrów, python przekazuje elementarny typ danych przez wartość, tzn. wykonuje lokalną kopię wartości do zmiennej lokalnej, ale przekazuje obiekt przez odniesienie, czyli wskaźniki do obiektu.

Przyznając się do powyższych dwóch punktów, wyjaśnijmy, co stało się z kodem Pythona. Jest to spowodowane tylko przekazaniem przez referencję dla obiektów, ale nie ma nic wspólnego z mutable/immutable, lub prawdopodobnie faktem, że instrukcja "def" jest wykonywana tylko raz, gdy jest zdefiniowana.

[] jest obiektem, więc python przekazuje odniesienie [] do a, tzn. a jest tylko wskaźnikiem do [], który leży w pamięci jako obiekt. Jest tylko jedna kopia z [] z wieloma odniesieniami do niego. Dla pierwszego foo(), lista [] zostanie zmieniona na 1 metodą dołączania. Zauważ jednak, że istnieje tylko jedna kopia obiektu list i Obiekt ten staje się teraz 1. Podczas uruchamiania drugiej foo (), to, co mówi strona effbot (elementy nie są już oceniane), jest błędne. a jest oceniany jako obiekt list, chociaż teraz zawartość obiektu jest 1. To jest efekt przechodzenia przez odniesienie! Wynikiem foo (3) może być łatwo wyprowadzać w ten sam sposób.

Aby jeszcze bardziej zweryfikować moją odpowiedź, przyjrzyjmy się dwóm dodatkowym kodom.

====== NIE. 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 (pierwszy jest zmienny, podczas gdy drugi jest niezmienny. Ale zmienność nie ma nic wspólnego z pytaniem). Żaden nie jest gdzieś w przestrzeni, ale wiemy, że jest tam i jest tylko jedna kopia żadnego tam. Więc za każdym razem, gdy foo jest wywoływane, elementy są oceniane (W przeciwieństwie do niektórych odpowiedzi, że to jest oceniany tylko raz) jako None, aby było jasne, odniesienie (lub adres) None. Następnie w foo element jest zmieniany na [], tzn. wskazuje na inny obiekt, który ma inny adres.

====== nie. 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) powoduje, że elementy wskazują na obiekt list [] o adresie, powiedzmy 111111111. treść listy zostaje zmieniona na 1 w funkcji foo w sequelu, ale adres nie został zmieniony, nadal 11111111. Następnie foo (2, []) jest idę. Chociaż [] in foo(2,[]) ma taką samą zawartość jak domyślny parametr [] podczas wywoływania foo (1), ich adres jest inny! Ponieważ podajemy parametr jawnie, {[9] } musi wziąć adres tego nowego [], powiedzmy 2222222, i zwrócić go po dokonaniu pewnej zmiany. Teraz Foo (3) jest wykonywane. ponieważ podano tylko x, elementy muszą ponownie przyjąć wartość domyślną. Jaka jest wartość domyślna? Jest ustawiany przy definiowaniu funkcji foo: obiektu list znajdującego się w 11111111. Więc przedmioty jest obliczany jako adres 11111111 posiadający element 1. Lista znajdująca się pod numerem 2222222 również zawiera jeden element 2, ale nie jest już wskazywana przez elementy. W konsekwencji dołączenie 3 spowoduje items [1,3].

Z powyższych wyjaśnień wynika, że strona effbot rekomendowana w zaakceptowanej odpowiedzi nie udzieliła odpowiedniej odpowiedzi na to pytanie. Co więcej, myślę, że punkt na stronie effbot jest zły. Myślę, że kod dotyczący interfejsu użytkownika.Przycisk jest poprawny:

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

Każdy przycisk może posiadać odrębną funkcję wywołania zwrotnego, która wyświetli inną wartość i. Mogę podać przykład, aby to pokazać:

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

Jeśli wykonamy x[7]() otrzymamy 7 zgodnie z oczekiwaniami, a x[9]() DA 9, kolejną wartość i.

 8
Author: user2384994,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-22 05:58:41

to nie jest wada projektowa . Każdy, kto się tym potknie, robi coś złego.

Widzę 3 przypadki, w których możesz napotkać ten problem:

  1. chcesz zmodyfikować argument jako efekt uboczny funkcji. W tym przypadku nigdy nie ma sensu mieć domyślny argument. Jedynym wyjątkiem jest nadużywanie listy argumentów, aby mieć atrybuty funkcji, np. cache={}, i nie należy oczekiwać, że funkcja będzie wywoływana z aktualnym kłótnia w ogóle.
  2. chcesz pozostawić argument niezmodyfikowany, ale przypadkowozmodyfikowałeś go. To błąd, napraw go.
  3. chcesz zmodyfikować argument do użycia wewnątrz funkcji, ale nie spodziewałeś się, że modyfikacja będzie widoczna poza funkcją. W takim przypadku musisz wykonać kopię argumentu, niezależnie od tego, czy był on domyślny, czy nie! Python nie jest językiem wywołania po wartości, więc nie tworzy kopii dla ciebie, musisz być wyraźny o to.

Przykład w pytaniu może należeć do kategorii 1 lub 3. To dziwne, że zarówno modyfikuje przekazaną listę i zwraca ją; powinieneś wybrać jedną lub drugą.

 6
Author: Mark Ransom,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-17 18:04:59
>>> def a():
>>>    print "a executed"
>>>    return []
>>> x =a()
a executed
>>> def b(m=[]):
>>>    m.append(5)
>>>    print m
>>> b(x)
[5]
>>> b(x)
[5, 5]
 3
Author: jai,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-05 09:49:47

Wystarczy zmienić funkcję na:

def notastonishinganymore(a = []): 
    '''The name is just a joke :)'''
    a = a[:]
    a.append(5)
    return a
 3
Author: ytpillai,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-06 21:29:08

Architektura

Przypisywanie wartości domyślnych w wywołaniu funkcji jest zapachem kodu.

def a(b=[]):
    pass

Jest to sygnatura funkcji, która nie jest dobra. Nie tylko z powodu problemów opisanych przez inne odpowiedzi. Nie wejdę do tego tutaj.

Ta funkcja ma na celu zrobienie dwóch rzeczy. Utwórz nową listę i uruchom funkcjonalność, najprawdopodobniej na wspomnianej liście.

Funkcje, które robią dwie rzeczy, są złe, jak uczymy się z czystego kodu praktyki.

Atakując ten problem polimorfizmem, rozszerzylibyśmy listę Pythona lub owinęlibyśmy ją w klasę, A następnie wykonalibyśmy na niej naszą funkcję.

[11]} ale czekaj, powiedz, lubię moje jednolinijkowe. Wiesz co? Kod to coś więcej niż tylko sposób na kontrolowanie zachowania sprzętu. Jest to sposób na:
  • Komunikowanie się z innymi programistami, praca nad tym samym kodem.

  • Możliwość zmiany zachowania sprzętu, gdy nowy powstają wymagania.

  • Bycie w stanie zrozumieć przepływ programu po odebraniu kodu ponownie po dwóch latach, aby wprowadzić wyżej wspomnianą zmianę.

NIE zostawiaj bomb zegarowych dla siebie, aby odebrać później.

Rozdzielając tę funkcję na dwie rzeczy, które robi, potrzebujemy klasy

class ListNeedsFives(object):
    def __init__(self, b=None):
        if b is None:
            b = []
        self.b = b

    def foo():
        self.b.append(5)

Wykonane przez

a = ListNeedsFives()
a.foo()
a.b

I dlaczego jest to lepsze niż zmiażdżenie całego powyższego kodu w jedną funkcję.

def dontdothis(b=None):
    if b is None:
        b = []
    b.append(5)
    return b

Dlaczego nie robić tego?

Jeśli nie uda Ci się w projekcie, Twój kod będzie żył. Najprawdopodobniej twoja funkcja będzie robić więcej niż to. Właściwym sposobem tworzenia kodu jest rozdzielenie kodu Na części atomowe o odpowiednio ograniczonym zakresie.

Konstruktor klasy jest bardzo powszechnie rozpoznawalnym komponentem dla każdego, kto zajmował się programowaniem obiektowym. Umieszczenie logiki obsługującej instancję listy w konstruktorze sprawia, że poznawcze obciążenie rozumienia co Kod robi mniejsze.

Metoda foo() nie zwraca listy, dlaczego nie?

Zwracając samodzielną listę, można założyć, że jest bezpiecznie zrobić to, co kiedykolwiek masz na to ochotę. Ale może nie być, ponieważ jest również współdzielony przez obiekt a. Zmuszanie użytkownika do odwoływania się do niej jako a.b przypomina mu, gdzie należy lista. Każdy nowy kod, który chce zmodyfikować a.b, zostanie naturalnie umieszczony w klasie, do której należy.

Funkcja def dontdothis(b=None): signature nie ma żadnego tych zalet.

 -3
Author: firelynx,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-10 15:43:47