"En Az Şaşkınlık" ve Değişken Temerrüt Argümanı

Jul 16 2009

Python ile yeterince uzun süre uğraşan herkes aşağıdaki sorun nedeniyle ısırıldı (veya parçalara ayrıldı):

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

Python acemi bu işlev her zaman yalnızca bir eleman içeren bir liste dönmek için beklenir: [5]. Bunun yerine sonuç çok farklı ve çok şaşırtıcı (acemi için):

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

Bir yöneticim bu özellikle ilk kez karşılaşmış ve bunu dilin "dramatik bir tasarım kusuru" olarak adlandırmıştı. Davranışın altında yatan bir açıklaması olduğunu söyledim ve içindekileri anlamamanız gerçekten çok şaşırtıcı ve beklenmedik bir durum. Ancak, şu soruyu yanıtlayamadım (kendi kendime): Varsayılan argümanı işlevin yürütülmesinde değil, işlev tanımında bağlamanın nedeni nedir? Tecrübeli davranışın pratik bir kullanımı olduğundan şüpheliyim (C de statik değişkenleri gerçekten hatalar olmadan kim kullandı?)

Düzenle :

Baczek ilginç bir örnek verdi. Yorumlarınızın çoğu ve özellikle Utaal ile birlikte, daha fazla ayrıntıya girdim:

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

Bana göre, tasarım kararı parametrelerin kapsamının nereye konulacağına bağlı gibi görünüyor: işlevin içinde mi yoksa onunla birlikte mi?

İşlevin içinde bağlama yapmak, xişlev çağrıldığında, tanımlanmadığında, belirtilen varsayılana etkin bir şekilde bağlı olduğu anlamına gelir , derin bir kusur sunacak bir şey: defsatır, bağlamanın parçası anlamında "hibrit" olacaktır ( fonksiyon nesnesi) tanımda ve kısımda (varsayılan parametrelerin atanması) fonksiyon çağırma zamanında gerçekleşir.

Gerçek davranış daha tutarlıdır: bu satırın her şeyi, o satır çalıştırıldığında değerlendirilir, yani fonksiyon tanımında.

Yanıtlar

1658 rob Jul 18 2009 at 04:29

Aslında, bu bir tasarım hatası değildir ve bunun nedeni dahili unsurlar veya performans değildir.
Basitçe, Python'daki işlevlerin birinci sınıf nesneler olması ve yalnızca bir kod parçası olmaması gerçeğinden gelir.

Bu şekilde düşünmeye başladığınız anda, o zaman tamamen mantıklı geliyor: Bir işlev, tanımına göre değerlendirilen bir nesnedir; varsayılan parametreler "üye verileridir" ve bu nedenle durumları bir çağrıdan diğerine değişebilir - tıpkı diğer herhangi bir nesnede olduğu gibi.

Her durumda, Effbot'un Python'daki Varsayılan Parametre Değerlerinde bu davranışın nedenleri hakkında çok güzel bir açıklaması vardır .
Bunu çok net buldum ve işlev nesnelerinin nasıl çalıştığına dair daha iyi bir bilgi için okumayı gerçekten öneririm.

278 EliCourtwright Jul 16 2009 at 01:11

Aşağıdaki koda sahip olduğunuzu varsayalım

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

def eat(food=fruits):
    ...

Eat bildirimini gördüğümde, en az şaşırtıcı olan şey, ilk parametre verilmediği takdirde, tuple'a eşit olacağını düşünmektir. ("apples", "bananas", "loganberries")

Ancak, kodun ilerleyen bölümlerinde şöyle bir şey yapıyorum:

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

o zaman varsayılan parametreler işlev bildirimi yerine işlev yürütmede bağlı olsaydı, meyvelerin değiştiğini keşfetmek (çok kötü bir şekilde) beni şaşırtabilirdi. Bu, fooyukarıdaki işlevinizin listeyi değiştirdiğini keşfetmekten daha şaşırtıcı bir IMO olurdu .

Gerçek sorun değişken değişkenlerde yatmaktadır ve tüm dillerde bir ölçüde bu sorun vardır. İşte bir soru: Java'da aşağıdaki koda sahip olduğumu varsayalım:

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?

Şimdi, haritam haritaya StringBufferyerleştirildiğinde anahtarın değerini kullanıyor mu yoksa anahtarı referans olarak saklıyor mu? Her iki durumda da biri şaşırır; ya nesneyi Mapiçine koyduğu değerle aynı değeri kullanarak çıkarmaya çalışan kişi ya da kullandığı anahtar kelimenin tam anlamıyla aynı nesne olmasına rağmen nesnesini geri getiremeyen kişi haritaya koymak için kullanıldı (aslında bu yüzden Python, değiştirilebilir yerleşik veri türlerinin sözlük anahtarı olarak kullanılmasına izin vermez).

Örneğiniz, Python'a yeni gelenlerin şaşırıp ısırılacağı iyi bir örnektir. Ama bunu "düzeltirsek", bunun sadece ısırılacağı farklı bir durum yaratacağını ve bunun daha da sezgisel olacağını iddia ediyorum. Ayrıca, değişebilir değişkenlerle uğraşırken durum her zaman böyledir; Her zaman birisinin hangi koda bağlı olarak sezgisel olarak bir veya zıt davranış bekleyebileceği durumlarla karşılaşırsınız.

Ben şahsen Python'un şu anki yaklaşımını seviyorum: varsayılan fonksiyon argümanları, fonksiyon tanımlandığında ve bu nesne her zaman varsayılan olduğunda değerlendirilir. Sanırım boş bir liste kullanarak özel durum oluşturabilirlerdi, ancak bu tür bir özel durum, geriye dönük uyumsuz olduğundan bahsetmiyorum bile daha fazla şaşkınlığa neden olur.

244 glglgl Jul 10 2012 at 21:50

Dokümantasyonun ilgili kısmı :

Varsayılan parametre değerleri, fonksiyon tanımı yürütüldüğünde soldan sağa doğru değerlendirilir. Bu, işlev tanımlandığında ifadenin bir kez değerlendirildiği ve her çağrı için aynı "önceden hesaplanmış" değerin kullanıldığı anlamına gelir. Bu, özellikle bir varsayılan parametrenin bir liste veya sözlük gibi değiştirilebilir bir nesne olduğunda anlamak için önemlidir: işlev nesneyi değiştirirse (örneğin bir listeye bir öğe ekleyerek), varsayılan değer aslında değiştirilir. Bu genellikle amaçlanan şey değildir. Bunu aşmanın bir yolu None, varsayılan olarak kullanmak ve bunu işlevin gövdesinde açıkça test etmektir, örneğin:

def whats_on_the_telly(penguin=None):
    if penguin is None:
        penguin = []
    penguin.append("property of the zoo")
    return penguin
121 Utaal Jul 16 2009 at 06:21

Python yorumlayıcısının iç işleyişi hakkında hiçbir şey bilmiyorum (ve ben de derleyiciler ve yorumlayıcılar konusunda uzman değilim), bu yüzden mantıksız veya imkansız bir şey önerirsem beni suçlamayın.

Python nesnelerinin değiştirilebilir olması koşuluyla , varsayılan argümanlar malzemelerini tasarlarken bunun dikkate alınması gerektiğini düşünüyorum. Bir liste oluşturduğunuzda:

a = []

tarafından referans verilen yeni bir liste almayı bekliyorsunuz a.

Niye olsun a=[]içinde

def x(a=[]):

Çağrıda değil işlev tanımında yeni bir liste başlatılsın mı? Bu "Kullanıcı daha sonra argüman sağlamaz soruyorsun kuruyor gibi hissedersiniz örneğini yeni bir liste ve arayan tarafından üretildi sanki kullanmak". Bunun yerine belirsiz olduğunu düşünüyorum:

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

kullanıcı, atanımlarken veya yürütürken karşılık gelen tarih saatini varsayılan olarak ayarlamak istiyor xmusunuz? Bu durumda, öncekinde olduğu gibi, varsayılan argüman "atama" işlevin ilk talimatıymış gibi aynı davranışı koruyacağım ( datetime.now()işlev çağrısında çağrılır). Öte yandan, kullanıcı tanım-zaman haritalamasını isterse şunları yazabilirdi:

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

Biliyorum, biliyorum: bu bir kapanış. Alternatif olarak Python, tanım zamanı bağlamayı zorlamak için bir anahtar sözcük sağlayabilir:

def x(static a=b):
83 LennartRegebro Jul 16 2009 at 01:54

Nedeni oldukça basittir ki, kod çalıştırıldığında bağlama yapılır ve fonksiyon tanımı çalıştırılır, yani ... fonksiyonlar tanımlandığında.

Şunu karşılaştırın:

class BananaBunch:
    bananas = []

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

Bu kod, aynı beklenmedik olaydan muzdariptir. muz bir sınıf özelliğidir ve bu nedenle, ona bir şeyler eklediğinizde, o sınıfın tüm örneklerine eklenir. Sebep tamamen aynı.

Sadece "Nasıl Çalışır?" ve nesneler oluşturulduğunda çalıştırın.

Evet, beklenmedik. Ancak kuruş düştüğünde, Python'un genel olarak çalışma şekline mükemmel bir şekilde uyuyor. Aslında, bu iyi bir öğretim yardımıdır ve bunun neden olduğunu anladığınızda, python'u çok daha iyi sallayacaksınız.

Bununla birlikte, herhangi bir Python eğitiminde belirgin bir şekilde yer alması gerektiğini söyledi. Çünkü sizin de bahsettiğiniz gibi, er ya da geç herkes bu sorunla karşılaşır.

69 DimitrisFasarakisHilliard Dec 09 2015 at 14:13

Neden iç gözlem yapmıyorsun?

Ben ediyorum gerçekten Python tarafından sunulan anlayışlı iç gözlem gerçekleştirildi (konuşup hiç kimsenin şaşırttı 2ve 3callables üzerine uygulanır).

Aşağıdaki funcgibi tanımlanan basit küçük bir işlev verildiğinde :

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

Python onunla karşılaştığında, yapacağı ilk şey code, bu işlev için bir nesne oluşturmak amacıyla onu derlemek olacaktır . Bu derleme adımı yapılırken, Python *[] değerini değerlendirir ve ardından varsayılan argümanları ( burada boş bir liste ) işlev nesnesinin kendisinde saklar . En önemli cevapta belirtildiği gibi: liste aartık işlevin bir üyesi olarak kabul edilebilir func.

Öyleyse, listenin fonksiyon nesnesi içinde nasıl genişletildiğini incelemek için önce ve sonra biraz iç gözlem yapalım . Bunun için kullanıyorum Python 3.x, Python 2 için de aynı şey geçerli (kullanım __defaults__veya func_defaultsPython 2; evet, aynı şey için iki isim).

Yürütme Öncesi İşlev:

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

Python bu tanımı yürüttükten sonra, belirtilen tüm varsayılan parametreleri ( a = []burada) alacak ve bunları __defaults__işlev nesnesinin özniteliğinde sıkıştıracaktır (ilgili bölüm: Çağrılabilirler):

>>> func.__defaults__
([],)

Tamam, __defaults__beklendiği gibi tek giriş olarak boş bir liste .

Yürütme Sonrası İşlev:

Şimdi bu işlevi çalıştıralım:

>>> func()

Şimdi bunlara __defaults__tekrar bakalım :

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

Şaşırdınız mı? Nesnenin içindeki değer değişir! İşleve yapılan ardışık çağrılar artık bu gömülü listnesneye eklenecektir :

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

İşte burada, bu 'kusurun' olmasının nedeni , varsayılan bağımsız değişkenlerin işlev nesnesinin bir parçası olmasıdır. Burada garip bir şey yok, hepsi biraz şaşırtıcı.

Bununla mücadele Noneetmenin ortak çözümü , varsayılan olarak kullanmak ve ardından işlev gövdesinde başlatmaktır:

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

İşlev gövdesi her seferinde yeniden çalıştırıldığı için, herhangi bir argüman iletilmediyse her zaman yeni bir boş liste alırsınız a.


İçindeki listenin __defaults__işlevde kullanılanla aynı olduğunu daha fazla doğrulamak funciçin işlevinizi işlev gövdesi içinde kullanılan idlisteyi döndürmek için değiştirebilirsiniz a. Ardından, listeye karşılaştırmak __defaults__(pozisyon [0]içinde __defaults__) ve bunlar gerçekten aynı liste örneğine atıfta nasıl göreceksiniz:

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

Hepsi iç gözlemin gücüyle!


* Python'un işlevin derlenmesi sırasında varsayılan bağımsız değişkenleri değerlendirdiğini doğrulamak için, aşağıdakileri çalıştırmayı deneyin:

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

fark input()edeceğiniz gibi, işlevin oluşturulması ve isme bağlanması işleminden önce çağrılır bar.

59 Brian Jul 16 2009 at 17:05

Çalışma zamanında nesneleri oluşturmanın daha iyi bir yaklaşım olacağını düşünürdüm. Şimdi daha az eminim, çünkü bazı yararlı özellikleri kaybedersiniz, ancak yeni başlayanların kafa karışıklığını önlemek için buna değebilir. Bunu yapmanın dezavantajları şunlardır:

1. Performans

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

Çağrı zamanı değerlendirmesi kullanılıyorsa, işleviniz bağımsız değişken olmadan her kullanıldığında pahalı işlev çağrılır. Ya her aramada pahalı bir fiyat ödersiniz ya da değeri harici olarak manuel olarak önbelleğe almanız gerekir, ad alanınızı kirletir ve ayrıntılar eklersiniz.

2. Bağlı parametreleri zorlama

Kullanışlı bir numara, bir lambda'nın parametrelerini , lambda oluşturulduğunda bir değişkenin mevcut bağlanmasına bağlamaktır. Örneğin:

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

Bu, sırasıyla 0,1,2,3 ... döndüren işlevlerin bir listesini döndürür. Davranış değiştirilirse, bunun yerine bağlayacak iiçin çağrı zamanlı tüm döndüğünü fonksiyonların bir listesini alacağı, böylece i değerine 9.

Aksi takdirde bunu uygulamanın tek yolu, i bağıyla daha fazla kapanış oluşturmaktır, yani:

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

3. İçgözlem

Kodu düşünün:

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

inspectModülü kullanarak argümanlar ve varsayılanlar hakkında bilgi alabiliriz.

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

Bu bilgiler belge oluşturma, meta programlama, dekoratörler vb. Şeyler için çok kullanışlıdır.

Şimdi, varsayılanların davranışının değiştirilebileceğini varsayalım, bu şuna eşdeğerdir:

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

Ancak biz kendi duygularını ve varsayılan argümanlar ne olduğunu görme olanağı kaybettim vardır . Nesneler inşa edilmediğinden, aslında işlevi çağırmadan onları asla ele geçiremeyiz. Yapabileceğimiz en iyi şey, kaynak kodunu saklamak ve bunu bir dize olarak döndürmektir.

55 LutzPrechelt Mar 30 2015 at 18:18

Python savunmasında 5 puan

  1. Basitlik : Davranış şu anlamda basittir: Çoğu insan bu tuzağa birkaç kez değil, yalnızca bir kez düşer.

  2. Tutarlılık : Python her zaman isimleri değil nesneleri iletir. Varsayılan parametre, tabii ki, işlev başlığının bir parçasıdır (işlev gövdesi değil). Bu nedenle, işlev çağrısı zamanında değil, modül yükleme zamanında (ve yuvalanmadıkça yalnızca modül yükleme zamanında) değerlendirilmelidir.

  3. Yararlılık : Frederik Lundh'un "Python'daki Varsayılan Parametre Değerleri" açıklamasında belirttiği gibi, mevcut davranış, gelişmiş programlama için oldukça yararlı olabilir. (Tutarlı kullanın.)

  4. Yeterli belgeler : En temel Python belgelerinde, eğitimde, konu yüksek sesle bir olarak açıklandı "Önemli uyarı" in ilk Bölümün alt bölümünde "Fonksiyon Tanımları Üzerine Daha" . Uyarı, başlıkların dışında nadiren uygulanan kalın yazı tipini bile kullanır. RTFM: Ayrıntılı kılavuzu okuyun.

  5. Meta öğrenme : Tuzağa düşmek aslında çok yardımcı bir andır (en azından derinlemesine düşünen bir öğreniciyseniz), çünkü daha sonra yukarıdaki "Tutarlılık" konusunu daha iyi anlayacaksınız ve bu size Python hakkında çok şey öğretecek.

53 ymv Jul 16 2009 at 02:15

Bu davranış şu şekilde açıklanabilir:

  1. işlev (sınıf vb.) bildirimi yalnızca bir kez yürütülür ve tüm varsayılan değer nesnelerini oluşturur
  2. her şey referansla aktarılır

Yani:

def x(a=0, b=[], c=[], d=0):
    a = a + 1
    b = b + [1]
    c.append(1)
    print a, b, c
  1. a değişmez - her atama çağrısı yeni int nesnesi oluşturur - yeni nesne yazdırılır
  2. b değişmez - yeni dizi varsayılan değerden oluşturulur ve yazdırılır
  3. c değişiklikler - işlem aynı nesne üzerinde gerçekleştirilir - ve yazdırılır
35 GlennMaynard Jul 16 2009 at 03:18

Sorduğun şey bunun nedeni:

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

dahili olarak buna eşdeğer değildir:

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()

göz ardı edeceğimiz func (Yok, Yok) özelliğinin açıkça çağrılması durumu dışında.

Başka bir deyişle, varsayılan parametreleri değerlendirmek yerine, neden her birini saklamayalım ve işlev çağrıldığında bunları değerlendirmeyelim?

Bir cevap muhtemelen tam oradadır - varsayılan parametrelere sahip her işlevi etkin bir şekilde bir kapanışa dönüştürecektir. Hepsi yorumlayıcıda gizlenmiş olsa ve tam anlamıyla kapanmamış olsa bile, verilerin bir yerde saklanması gerekir. Daha yavaş olur ve daha fazla hafıza kullanır.

35 hynekcer Nov 23 2012 at 01:09

1) "Değişken Temerrüt Argümanı" olarak adlandırılan problem, genel olarak şunu gösteren özel bir örnektir:
"Bu probleme sahip tüm fonksiyonlar , gerçek parametrede benzer yan etki probleminden de muzdariptir ,"
Bu, fonksiyonel programlama kurallarına aykırıdır genellikle önlenemez ve her ikisi birlikte düzeltilmelidir.

Misal:

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]

Çözüm : bir kopya
Kesinlikle güvenli bir çözüm, önce copyveya deepcopygirdi nesnesine ve sonra kopya ile ne yaparsa yapsın.

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

Birçok yerleşik değiştirilebilir tipler gibi bir kopya yöntemine sahip some_dict.copy()ya some_set.copy()ya da benzeri kolay kopyalanabilir somelist[:]veya list(some_list). Her nesne tarafından copy.copy(any_object)veya daha kapsamlı bir şekilde kopyalanabilir copy.deepcopy()(ikincisi, değiştirilebilir nesne değiştirilebilir nesnelerden oluşuyorsa yararlıdır). Bazı nesneler temelde "dosya" nesnesi gibi yan etkilere dayanır ve kopyalanarak anlamlı bir şekilde çoğaltılamaz. kopyalama

Benzer bir SO sorusu için örnek problem

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]

Bu işlev tarafından döndürülen bir örneğin herhangi bir genel özniteliğine kaydedilmemelidir . ( Örneğin özel özniteliklerinin bu sınıfın veya alt sınıfların dışından değiştirilmemesi gerektiğini varsayarsak, yani _var1bir özel özniteliktir)

Sonuç:
Girdi parametreleri nesneleri yerinde değiştirilmemeli (değiştirilmemeli) veya işlev tarafından döndürülen bir nesneye bağlanmamalıdır. (Eğer yan etkiler olmadan programlamayı tercih edersek ki bu şiddetle tavsiye edilir. "Yan etki" ile ilgili Wiki'ye bakın (İlk iki paragraf bu bağlamda önemlidir.)

2)
Yalnızca gerçek parametre üzerindeki yan etki gerekliyse, ancak varsayılan parametre üzerinde istenmeyen bir etki söz konusu ise, o zaman yararlı çözüm def ...(var1=None): if var1 is None: var1 = [] Diğer'dir ..

3) Bazı durumlarda, varsayılan parametrelerin değiştirilebilir davranışı yararlıdır .

31 Ben May 23 2011 at 11:24

Bunun aslında varsayılan değerlerle hiçbir ilgisi yoktur, bunun dışında, değişken varsayılan değerlere sahip işlevler yazdığınızda genellikle beklenmeyen bir davranış olarak ortaya çıkar.

>>> 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]

Bu kodda görünürde varsayılan değerler yok, ancak tamamen aynı sorunu yaşıyorsunuz.

Sorun olduğunu fooedilir değiştirerek arayan bu beklemediğini zaman, arayanın gelen geçirilen bir değişken değişken. İşleve şöyle bir şey çağrılsaydı, böyle bir kod iyi olurdu append_5; daha sonra arayan, ilettikleri değeri değiştirmek için işlevi çağırır ve davranış beklenir. Ancak böyle bir işlevin varsayılan bir argüman alması pek olası değildir ve muhtemelen listeyi döndürmez (çünkü arayan kişi bu listeye zaten bir referanstır; az önce gönderdiği).

fooVarsayılan bir bağımsız değişkene sahip orijinaliniz , aaçıkça aktarılıp aktarılmadığını veya varsayılan değeri alıp almadığını değiştirmemelidir . Bağlamdan / addan / belgeden bağımsız değişkenlerin değiştirilmesinin beklendiği açık olmadığı sürece kodunuz değişebilir bağımsız değişkenleri bırakmalıdır. Değişken değerleri argümanlar olarak yerel geçiciler olarak kullanmak, Python'da olsak da olmasak da ve varsayılan argümanlar olup olmadığına bakılmaksızın son derece kötü bir fikirdir.

Bir şeyi hesaplama sırasında yerel bir geçiciyi yıkıcı bir şekilde manipüle etmeniz gerekiyorsa ve manipülasyonunuzu bir argüman değerinden başlatmanız gerekiyorsa, bir kopya oluşturmanız gerekir.

27 Stéphane Mar 27 2015 at 06:14

Zaten meşgul bir konu, ancak burada okuduğumdan, aşağıdakiler dahili olarak nasıl çalıştığını anlamama yardımcı oldu:

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
25 JasonBaker Jul 16 2009 at 06:18

Bu bir performans optimizasyonu. Bu işlevselliğin bir sonucu olarak, bu iki işlev çağrısından hangisinin daha hızlı olduğunu düşünüyorsunuz?

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

print_tuple()        #1
print_tuple((1,2,3)) #2

Sana bir ipucu vereceğim. İşte sökme işlemi (bkz.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

Tecrübeli davranışın pratik bir kullanımı olduğundan şüpheliyim (C de statik değişkenleri gerçekten hatalar olmadan kim kullandı?)

Gördüğünüz gibi, orada olan değişmez varsayılan argümanlar kullanırken bir performans yararı. Bu, sık olarak adlandırılan bir işlevse veya varsayılan bağımsız değişkenin oluşturulması uzun sürüyorsa bir fark yaratabilir. Ayrıca, Python'un C olmadığını aklınızda bulundurun. C'de hemen hemen bedava sabitler vardır. Python'da bu avantaja sahip değilsiniz.

25 AaronHall May 01 2016 at 23:20

Python: Değişken Varsayılan Argüman

Varsayılan argümanlar, fonksiyon bir fonksiyon nesnesine derlendiğinde değerlendirilir. İşlev tarafından birden çok kez kullanıldığında, bunlar aynı nesnedir ve kalırlar.

Değiştirilebilir olduklarında, mutasyona uğradıklarında (örneğin, ona bir eleman ekleyerek), ardışık çağrılarda mutasyona uğramış kalırlar.

Her seferinde aynı nesne oldukları için mutasyona uğramış kalırlar.

Eşdeğer kod:

İşlev nesnesi derlendiğinde ve başlatıldığında liste işleve bağlı olduğundan, bu:

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

neredeyse tam olarak buna eşdeğerdir:

_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

Gösteri

İşte bir gösteri - her başvuruda bulunduklarında aynı nesne olduklarını doğrulayabilirsiniz.

  • listenin, fonksiyon bir fonksiyon nesnesine derlemeyi bitirmeden önce oluşturulduğunu görmek,
  • listeye her başvurulduğunda id'nin aynı olduğunu gözlemleyerek,
  • onu kullanan işlev ikinci kez çağrıldığında listenin değiştiğini gözlemleyerek,
  • Çıktının kaynaktan yazdırıldığı sırayı gözlemleyerek (sizin için uygun şekilde numaralandırdım):

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()

ve şununla çalıştırılıyor 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']

Bu, "En Az Şaşkınlık" ilkesini ihlal ediyor mu?

Bu yürütme sırası, yeni Python kullanıcıları için sıklıkla kafa karıştırıcıdır. Python yürütme modelini anlarsanız, oldukça beklenen bir hale gelir.

Yeni Python kullanıcılarına olağan talimat:

Ancak bu nedenle, yeni kullanıcılara her zamanki talimat, bunun yerine varsayılan argümanlarını oluşturmaktır:

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

Bu, işleve varsayılandan başka bir argüman alıp almadığımızı söylemek için bir sentinel nesne olarak Yok tekli kullanır. Eğer bir argüman almazsak, aslında []varsayılan olarak yeni bir boş liste kullanmak isteriz .

Kontrol akışıyla ilgili eğitim bölümünde söylendiği gibi:

Varsayılanın sonraki aramalar arasında paylaşılmasını istemiyorsanız, bunun yerine işlevi şu şekilde yazabilirsiniz:

def f(a, L=None):
    if L is None:
        L = []
    L.append(a)
    return L
24 Baczek Jul 16 2009 at 19:19

En kısa cevap muhtemelen "tanım yürütmedir" olacaktır, bu nedenle tüm argüman kesin bir anlam ifade etmiyor. Daha uydurma bir örnek olarak, şundan alıntı yapabilirsiniz:

def a(): return []

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

Öntanımlı argüman ifadelerini ifadenin çalıştırma anında çalıştırılmamasının defkolay olmadığını veya mantıklı olmadığını veya her ikisini birden göstermenin yeterli olacağını umuyoruz .

Yine de, varsayılan kurucuları kullanmaya çalıştığınızda bunun bir aldatmaca olduğuna katılıyorum.

21 DmitryMinkovsky Apr 25 2012 at 02:43

Aşağıdakileri dikkate alırsanız bu davranış şaşırtıcı değildir:

  1. Atama girişimleri üzerine salt okunur sınıf özniteliklerinin davranışı ve
  2. Fonksiyonlar nesnelerdir (kabul edilen cevapta iyi açıklanmıştır).

(2) ' nin rolü bu ileti dizisinde kapsamlı bir şekilde ele alınmıştır. (1) muhtemelen şaşkınlık yaratan faktördür, çünkü bu davranış diğer dillerden geldiğinde "sezgisel" değildir.

(1) Python derslerinde sınıflarla ilgili olarak açıklanmıştır . Salt okunur bir sınıf özniteliğine bir değer atama girişiminde:

... en içteki kapsamın dışında bulunan tüm değişkenler salt okunurdur ( böyle bir değişkene yazma girişimi, en içteki kapsamda yeni bir yerel değişken yaratır ve aynı adlandırılmış dış değişkeni değiştirmeden bırakır ).

Orijinal örneğe dönüp yukarıdaki noktaları göz önünde bulundurun:

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

Burada foobir nesne ve abir özniteliği foo(mevcut foo.func_defs[0]). Yana abir listedir, adeğişken ve böylece bir okuma-yazma özelliğidir foo. İşlev başlatıldığında imza tarafından belirtildiği gibi boş listeye başlatılır ve işlev nesnesi var olduğu sürece okuma ve yazma için kullanılabilir.

fooBir varsayılanı geçersiz kılmadan çağrı yapmak o varsayılanın değerini kullanır foo.func_defs. Bu durumda, işlev nesnesinin kod kapsamı içinde foo.func_defs[0]kullanılır a. Nesnenin bir parçası olan ve kodun yürütülmesi arasında devam eden değişiklik adeğişiklikleri .foo.func_defs[0]foofoo

Şimdi, bunu diğer dillerin varsayılan bağımsız değişken davranışını taklit etme konusundaki dokümantasyondaki örnekle karşılaştırın , öyle ki işlev imza varsayılanları işlev her çalıştırıldığında kullanılır:

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

Alarak (1) ve (2) bu istenilen davranışı gerçekleştirir neden dikkate bir görebilir:

  • Zaman foofonksiyonu nesne örneği, foo.func_defs[0]ayarlanır Nonedeğişken olmayan bir obje.
  • İşlev varsayılanlarla çalıştırıldığında ( Lişlev çağrısında herhangi bir parametre belirtilmeden ), foo.func_defs[0]( None) yerel kapsamda olarak kullanılabilir L.
  • Bundan sonra L = [], atama başarılı olamaz foo.func_defs[0], çünkü bu öznitelik salt okunurdur.
  • Per (1) 'e göre , yerel kapsamda ayrıca adlandırılan yeni bir yerel değişken Lyaratılır ve işlev çağrısının geri kalanı için kullanılır. foo.func_defs[0]bu nedenle gelecekteki çağrılarda değişmeden kalır foo.
20 hugo24 Feb 28 2013 at 18:10

Yok kullanarak basit bir çözüm

>>> 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]
19 Alexander Sep 12 2015 at 13:00

Bir işleve varsayılan bir liste değeri geçirmek için alternatif bir yapı göstereceğim (sözlüklerle eşit derecede iyi çalışır).

Başkalarının kapsamlı bir şekilde yorumladığı gibi, liste parametresi, çalıştırıldığı zamanın aksine tanımlandığında işleve bağlıdır. Listeler ve sözlükler değiştirilebilir olduğundan, bu parametreye yapılacak herhangi bir değişiklik, bu işleve yönelik diğer çağrıları etkileyecektir. Sonuç olarak, işleve sonraki çağrılar, işleve yapılan diğer çağrılarla değiştirilmiş olabilecek bu paylaşılan listeyi alacaktır. Daha da kötüsü, iki parametre bu işlevin paylaşılan parametresini aynı anda kullanıyor ve diğerinin yaptığı değişikliklerden habersiz.

Yanlış Yöntem (muhtemelen ...) :

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

Şunları kullanarak tek ve aynı nesne olduklarını doğrulayabilirsiniz id:

>>> id(a)
5347866528

>>> id(b)
5347866528

Brett Slatkin'in "Etkili Python: Daha İyi Python Yazmanın 59 Spesifik Yolu" na göre, Madde 20: NoneDinamik varsayılan argümanları belirtmek için Kullanım ve Dokümanlar (s. 48)

Python'da istenen sonucu elde etmenin kuralı, varsayılan bir değer sağlamak Noneve docstring'deki gerçek davranışı belgelemektir.

Bu uygulama, işleve yapılan her çağrının ya varsayılan listeyi almasını ya da işleve iletilen listeyi almasını sağlar.

Tercih Edilen Yöntem :

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]

Programcının varsayılan liste parametresinin paylaşılmasını amaçladığı 'Yanlış Yöntem' için meşru kullanım durumları olabilir, ancak bu, kuraldan daha büyük olasılıkla istisnadır.

17 Marcin Mar 21 2012 at 00:22

Buradaki çözümler:

  1. NoneVarsayılan değeriniz (veya nonce object) olarak kullanın ve çalışma zamanında değerlerinizi oluşturmak için bunu açın; veya
  2. lambdaVarsayılan parametreniz olarak a kullanın ve varsayılan değeri elde etmek için bunu bir try bloğu içinde çağırın (bu, lambda soyutlamasının gerektirdiği türden bir şeydir).

İkinci seçenek güzeldir çünkü işlevin kullanıcıları, zaten var olan bir çağrılabilir (örneğin a type)

16 joedborg Jan 15 2013 at 18:02

Nesneyi değiştirerek (ve dolayısıyla kapsamla bağ kurarak) bunu aşabilirsiniz:

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

Çirkin, ama işe yarıyor.

16 Saish Sep 12 2014 at 05:05

Bunu yaptığımızda:

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

... biz argüman atamak abir karşı isimsiz Arayan değerini geçmez ise, listede.

Bu tartışmada işleri daha basit hale getirmek için, adsız listeye geçici olarak bir ad verelim. Nasıl olur pavlo?

def foo(a=pavlo):
   ...

Herhangi bir zamanda, arayan bize ne aolduğunu söylemezse , yeniden kullanırız pavlo.

Eğer pavlodeğiştirilebilir (değiştirilebilir) ise ve fooonu değiştirmeyi bitirirse, bir dahaki sefere fark ettiğimiz bir efekt foobelirtilmeden çağrılır a.

İşte gördüğünüz şey bu (Unutmayın, pavlo[] olarak başlatılmıştır):

 >>> foo()
 [5]

Şimdi, pavlo[5].

foo()Tekrar arama tekrar değiştirir pavlo:

>>> foo()
[5, 5]

Belirtme açağıran foo()sağlar pavlodokunulmaz.

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

Yani, pavlohala [5, 5].

>>> foo()
[5, 5, 5]
16 bgreen-litl Feb 06 2015 at 04:44

Bazen bu davranışı aşağıdaki modele alternatif olarak kullanıyorum:

singleton = None

def use_singleton():
    global singleton

    if singleton is None:
        singleton = _make_singleton()

    return singleton.use_me()

Eğer singletonsadece tarafından kullanılan use_singleton, bir yedek olarak aşağıdaki deseni gibi:

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

Bunu harici kaynaklara erişen istemci sınıflarını örneklemek ve ayrıca hafızaya alma için dikteler veya listeler oluşturmak için kullandım.

Bu modelin iyi bilindiğini düşünmediğim için, gelecekteki yanlış anlaşılmalara karşı korunmak için kısa bir yorum yapıyorum.

13 ChristosHayward Jul 17 2009 at 02:17

Doğru olabilir:

  1. Birisi her dili / kitaplık özelliğini kullanıyor ve
  2. Buradaki davranışı değiştirmek yanlış olur, ancak

Yukarıdaki her iki özelliğe sahip olmak ve yine de başka bir noktaya değinmek tamamen tutarlıdır:

  1. Bu kafa karıştırıcı bir özelliktir ve Python'da talihsizdir.

Diğer cevaplar, ya da en azından bazıları ya 1. ve 2. noktaları yapar ama 3. noktayı koymaz ya da 3. noktayı yapar ve 1. ve 2. noktaları önemsiz gösterir. Ancak üçü de doğru.

Burada ortadaki atları değiştirmenin önemli bir kırılma gerektireceği ve Python'u Stefano'nun açılış pasajını sezgisel olarak işleyecek şekilde değiştirerek daha fazla sorun yaratılabileceği doğru olabilir. Ve Python'un iç kısımlarını iyi bilen birinin bir mayın tarlasının sonuçlarını açıklayabileceği doğru olabilir. Ancak,

Mevcut davranış Pythonic değildir ve Python başarılıdır çünkü dil hakkında çok az şey bu kadar yakın herhangi bir yerde en az şaşkınlık ilkesini ihlal eder . Onu kökünden söküp atmanın akıllıca olup olmayacağı gerçek bir sorundur. Bu bir tasarım hatasıdır. Davranışın izini sürmeye çalışarak dili çok daha iyi anlıyorsanız, C ++ 'nın tüm bunları ve daha fazlasını yaptığını söyleyebilirim; Örneğin, ince işaretçi hatalarında gezinerek çok şey öğrenirsiniz. Ancak bu Pythonic değil: Python'u bu davranış karşısında sebat edecek kadar önemseyen insanlar dile ilgi duyan insanlardır çünkü Python diğer dillerden çok daha az sürpriz yaşar. Dabbler ve meraklılar, bir şeyi çalıştırmanın ne kadar kısa sürdüğünü görünce şaşırdıklarında Pythonist olurlar - bir tasarım nedeniyle değil - yani gizli mantık bulmacası - Python'a ilgi duyan programcıların sezgilerine aykırıdır. çünkü Sadece Çalışıyor .

10 MarkRansom Oct 18 2017 at 00:38

Bu bir tasarım hatası değil . Buna takılıp kalan herkes yanlış bir şey yapıyor.

Bu problemle nerede karşılaşabileceğinizi gördüğüm 3 durum var:

  1. Fonksiyonun bir yan etkisi olarak argümanı değiştirmeyi planlıyorsunuz. Bu durumda varsayılan bir argümana sahip olmak hiçbir zaman mantıklı değildir. Bunun tek istisnası, bağımsız değişken listesini örneğin işlev özniteliklerine sahip olmak için kötüye kullandığınız zamandır cache={}ve işlevi gerçek bir bağımsız değişkenle çağırmanız beklenmez.
  2. Sen değiştirilmemiş argüman bırakmak niyetinde, ancak yanlışlıkla vermedi değiştirmek. Bu bir hata, düzeltin.
  3. İşlevin içinde kullanım için bağımsız değişkeni değiştirmeyi planlıyorsunuz, ancak değişikliğin işlevin dışında görüntülenmesini beklemiyorsunuz. Bu durumda , varsayılan olsun ya da olmasın, argümanın bir kopyasını almanız gerekir ! Python, değer bazında bir çağrı dili değildir, bu yüzden sizin için bir kopya oluşturmaz, bu konuda açık olmanız gerekir.

Sorudaki örnek kategori 1 veya 3'e girebilir. Hem geçirilen listeyi değiştirmesi hem de onu döndürmesi tuhaftır; birini veya diğerini seçmelisiniz.

9 Norfeldt Jul 22 2013 at 14:35

Bu "hata" bana çok fazla mesai saati verdi! Ama bunun potansiyel bir kullanımını görmeye başlıyorum (ama yine de yürütme zamanında olmasını isterdim)

Size yararlı bir örnek olarak gördüğüm şeyi vereceğim.

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()

aşağıdakileri yazdırır

Attempt to fix it
Attempt to fix it by another way
Attempt to fix it
Attempt to fix it by another way
8 ytpillai May 26 2015 at 06:04

Sadece işlevi şu şekilde değiştirin:

def notastonishinganymore(a = []): 
    '''The name is just a joke :)'''
    a = a[:]
    a.append(5)
    return a
7 user2384994 Aug 22 2013 at 12:58

Sanırım bu sorunun cevabı, python'un verileri parametreye nasıl geçirdiğinde (değere göre veya referansa göre), değişkenliğe veya python'un "def" ifadesini nasıl işlediğine değil.

Kısa bir giriş. Birincisi, python'da iki tür veri türü vardır, biri sayılar gibi basit temel veri türü ve diğer veri türü nesnelerdir. İkincisi, verileri parametrelere aktarırken, python değere göre temel veri türünü geçirir, yani değerin yerel bir değişkene yerel bir kopyasını oluşturur, ancak nesneyi referansla, yani nesneye işaretçilerle iletir.

Yukarıdaki iki noktayı kabul ederek python koduna ne olduğunu açıklayalım. Bu sadece nesneler için referans olarak geçme nedeniyledir, ancak değişken / değişmez ile hiçbir ilgisi yoktur veya muhtemelen "def" ifadesinin tanımlandığında yalnızca bir kez çalıştırılması gerçeğidir.

[] bir nesnedir, bu yüzden python [] 'nin referansını iletir a, yani asadece bellekte bir nesne olarak bulunan []' a bir göstericidir. Ancak [] 'nın birçok referansı olan tek bir kopyası var. İlk foo () için, [] listesi ekleme yöntemi ile 1 olarak değiştirilir . Ancak liste nesnesinin yalnızca bir kopyası olduğuna ve bu nesnenin artık 1 olduğuna dikkat edin . İkinci foo () çalıştırılırken, effbot web sayfasının söylediği (öğeler artık değerlendirilmez) yanlıştır. aşimdi nesnenin içeriği 1 olmasına rağmen liste nesnesi olarak değerlendirilir . Bu, referansla geçmenin etkisidir! Foo (3) 'ün sonucu da aynı şekilde kolaylıkla türetilebilir.

Cevabımı daha fazla doğrulamak için, iki ek koda bir göz atalım.

====== Hayır. 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]

[]bir nesnedir, öyledir None(ilki değişebilirken ikincisi değişmezdir. Ancak değişebilirliğin soruyla hiçbir ilgisi yoktur). Hiçbiri uzayda bir yerde değil ama orada olduğunu biliyoruz ve orada Yok'un yalnızca bir kopyası var. Bu nedenle, foo her çağrıldığında, öğeler Yok olarak değerlendirilir (yalnızca bir kez değerlendirilen bazı yanıtların aksine), açık olması için Yok'un referansı (veya adresi). Daha sonra foo'da öğe [] olarak değiştirilir, yani farklı bir adresi olan başka bir nesneyi işaret eder.

====== Hayır.3 =======

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

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

Foo (1) 'in çağrılması, öğelerin 11111111 adresli bir liste nesnesine [] işaret etmesini sağlar. Listenin içeriği devam dizisindeki foo işlevinde 1 olarak değiştirilir , ancak adres değişmez, yine de 11111111 Sonra foo (2, []) geliyor. Foo (2, []) içindeki [], foo (1) çağrılırken [] varsayılan parametresi ile aynı içeriğe sahip olsa da, adresleri farklıdır! Parametreyi açıkça sağladığımız için items, bu yeninin adresini [], diyelim ki 2222222'yi almalı ve bazı değişiklikler yaptıktan sonra onu iade etmeliyiz. Şimdi foo (3) çalıştırılır. yalnızca xsağlandığından, öğelerin varsayılan değerini yeniden alması gerekir. Varsayılan değer nedir? Foo işlevi tanımlanırken ayarlanır: 11111111'de bulunan liste nesnesi. Dolayısıyla, öğeler, bir öğesi 1'e sahip 11111111 adresi olarak değerlendirilir. 2222222'de bulunan liste ayrıca bir öğe 2 içerir, ancak öğelerle işaret edilmemiştir. Daha. Sonuç olarak, 3'ün bir eki items[1,3] yapacaktır .

Yukarıdaki açıklamalardan, kabul edilen cevapta önerilen effbot web sayfasının bu soruya uygun bir cevap vermediğini görebiliyoruz. Dahası, effbot web sayfasındaki bir noktanın yanlış olduğunu düşünüyorum. UI.Button ile ilgili kodun doğru olduğunu düşünüyorum:

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

Her düğme, farklı bir değeri görüntüleyecek farklı bir geri arama işlevi tutabilir i. Bunu göstermek için bir örnek verebilirim:

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

Eğer çalıştırırsak x[7]()beklendiği gibi 7'yi elde ederiz ve x[9]()9'un başka bir değerini veririz i.

6 MisterMiyagi Dec 15 2018 at 19:09

TLDR: Define-time varsayılanları tutarlıdır ve kesinlikle daha açıklayıcıdır.


Bir işlevin tanımlanması iki kapsamı etkiler: işlevi içeren tanımlayıcı kapsam ve işlevin içerdiği yürütme kapsamı . Kapsamlara haritayı nasıl blokladığı oldukça açık olsa da, soru nereye def <name>(<args=defaults>):ait olduğudur:

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

def nameBölüm olmalıdır tanımlayan kapsamında değerlendirmek - istediğimiz namesonuçta orada kullanılabilir olması. İşlevi yalnızca kendi içinde değerlendirmek, onu erişilemez kılacaktır.

Yana parametersürekli bir isimdir, biz aynı anda "değerlendirmek" olabilir def name. Bu aynı zamanda işlevi name(parameter=...):çıplak yerine bilinen bir imzayla üretme avantajına da sahiptir name(...):.

Şimdi, ne zaman değerlendirilmeli default?

Tutarlılık zaten "tanımda" diyor: diğer her şey def <name>(<args=defaults>):de en iyi tanımda değerlendirilir. Bazı kısımlarını ertelemek şaşırtıcı bir seçim olacaktır.

İki seçenek de eşdeğer değildir: defaultTanım zamanında değerlendirilirse, yine de yürütme süresini etkileyebilir. Eğer defaultyürütme sırasında değerlendirilir, bu olamaz tanım süresini etkiler. "Tanımda" seçeneğinin seçilmesi, her iki durumun da ifade edilmesine izin verirken, "yürütme sırasında" seçeneği yalnızca birini ifade edebilir:

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
    ...
4 PrzemekD Jan 03 2019 at 14:38

Diğer yanıtların her biri, bunun neden gerçekten hoş ve istenen bir davranış olduğunu ya da neden buna ihtiyaç duymamanız gerektiğini açıklıyor. Benimki, dili kendi iradesine göre bükme hakkını kullanmak isteyenler için, tersi değil.

Bu davranışı, varsayılan değerinde bırakılan her konumsal argüman için aynı örneği yeniden kullanmak yerine varsayılan değeri kopyalayacak bir dekoratörle "düzelteceğiz".

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

Şimdi bu dekoratörü kullanarak işlevimizi yeniden tanımlayalım:

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

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

Bu, özellikle birden çok argüman alan işlevler için düzgündür. Karşılaştırmak:

# 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

ile

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

Anahtar kelime değiştirgeleri kullanmaya çalışırsanız yukarıdaki çözümün bozulacağını unutmamak önemlidir, örneğin:

foo(a=[4])

Dekoratör buna izin verecek şekilde ayarlanabilir, ancak bunu okuyucu için bir alıştırma olarak bırakıyoruz;)