Soru “En Az Şaşkınlık” ve Değişken Varsayılan Tartışma


Python ile yeterince uzun süre ilgilenen herkes, aşağıdaki sorunla ısırıldı (veya parçalara ayrıldı):

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

Python acemi, bu işlevin bir listeyi daima tek bir öğeyle döndürmesini beklerdi: [5]. Sonuç bunun yerine çok farklı ve çok şaşırtıcı (bir acemi için):

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

Benim bir yöneticim, bir kez bu özelliği ile ilk karşılaşmasını vardı ve ona dilin "dramatik bir tasarım hatası" adını verdi. Davranışın altında yatan bir açıklama olduğunu söyledim ve içselleri anlamıyorsanız gerçekten çok şaşırtıcı ve beklenmedik. Bununla birlikte, şu soruya cevap veremedim: varsayılan argümanı fonksiyon tanımında bağlama işlevinin yerine getirilmemesinin nedeni nedir? Tecrübeli davranışın pratik bir kullanımdan (şüphe duymadan hata yapmayan C'deki statik değişkenleri kim kullandığını) sanmıyorum.

Düzenle:

Baczek ilginç bir örnek yaptı. Özellikle yorumlarınız ve Utaal'ın çoğuyla birlikte daha ayrıntılı bir şekilde hazırladım:

>>> 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ı nereye koyacağına göre göründü: fonksiyonun içinde ya da onunla birlikte "birlikte" mi?

Bağlamın işlev içinde yapılması, x işlev çağrıldığında, tanımlı değil, derin bir kusur sunacak bir şey olduğunda, belirtilen varsayılan değere etkin olarak bağlanır: def satır, bağlama işlevinin (işlev nesnesinin) bir kısmının tanımda gerçekleşeceği ve işlev çağırma zamanında bölümün (varsayılan parametrelerin atama) olacağı anlamında "melez" olacaktır.

Asıl davranış daha tutarlıdır: o satırın her şeyi, bu satır yürütüldüğünde, yani fonksiyon tanımında değerlendirilir.


2057
2017-07-15 18:00


Menşei


Tamamlayıcı soru - Değişken varsayılan argümanlar için iyi kullanımlar - Jonathan
Değişken argümanların, ortalama bir insan için en az şaşkınlık ilkesini ihlal ettiğinden şüphe etmiyorum ve oraya yeni başlayanları gördüm, sonra posta listelerini posta gönderme ile kahramanca değiştirdim. Yine de, değişken argümanlar hala Python Zen (Pep 20) ile uyumludur ve "sert çekirdek python programcıları tarafından anlaşılan / anlaşılan" Hollandaca için "açık" ifadesine düşmektedir. Doc dizesi ile önerilen geçici çözüm en iyisidir, ancak doc dizelerine ve herhangi bir (yazılı) belgeye karşı direnç günümüzde çok nadir değildir. Şahsen, bir dekoratör (@fixed_defaults) tercih ederim. - Serge
Tartışmamda karşılığım şu: "Neden isteğe bağlı olarak bu işleve geçebileceğimiz bir değişken olabilir bir değişken döndüren bir işlev yaratmanız gerekiyor? Ya bir değişkeni değiştirir veya yeni bir tane oluşturur. Neden ihtiyacın var? Bir işlevle her ikisini birden yapmak için? Ve neden yorumcunuz kodunuza üç satır eklemeden bunu yapmanıza izin vermek için yeniden yazılmalıdır? " Çünkü yorumcunun işlev tanımlarını ve çağrışımlarını burada işleme biçimini yeniden yazmaktan bahsediyoruz. Zorlukla gerekli bir kullanım durumu için yapılacak çok şey var. - Alan Leuthard
"Python acemi, bu işlevin bir listeyi daima tek bir öğeyle döndürmesini beklerdi: [5]"Ben bir Python acemiim ve bunu beklemezdim çünkü belli ki foo([1]) dönecek [1, 5], değil [5]. Söylemen gereken şey, acemi bir fonksiyonun bekleyeceği parametre içermeyen her zaman dönecek [5]. - symplectomorphic
İçin Python öğreticisinde örnek, neden ki if L is None: gerekli? Bu testi kaldırdım ve farketmedi - sdaffa23fdsf


Cevaplar:


Aslında bu bir tasarım hatası değil, dahili veya performans nedeniyle değil.
Python'daki fonksiyonların sadece birinci sınıf nesneler olduğu ve sadece bir kod parçası olmadığı gerçeğinden gelmektedir.

Bu şekilde düşünmeye başlar başlamaz, o zaman tamamen mantıklıdır: Bir işlev tanımında değerlendirilen bir nesnedir; varsayılan parametreler "üye verileri" türündendir ve bu nedenle durumları bir çağrıdan diğerine değişebilir - tam olarak başka herhangi bir nesnede olduğu gibi.

Her halükarda, Effbot'un bu davranışın nedenlerini çok güzel bir açıklaması var. Python'da Varsayılan Parametre Değerleri.
Bunu çok net buldum ve fonksiyon nesnelerinin nasıl çalıştığına dair daha iyi bir bilgi için okumanızı tavsiye ediyorum.


1353
2017-07-17 21:29



Yukarıdaki cevabı okuyan herkese, bağlantılı Effbot makalesini okumak için zaman ayırmanızı önemle tavsiye ederim. Diğer tüm yararlı bilgilerin yanı sıra, bu dil özelliklerinin sonuçta önbelleğe alma / not tutma için nasıl kullanılabileceği kısmı bilmek çok kullanışlıdır! - Cam Jackson
Birinci sınıf bir nesne olsa bile, hala bir tasarımın tasarlanabileceği düşünülebilir. kod Her bir varsayılan değer için nesne ile birlikte saklanır ve işlev her çağrıldığında yeniden değerlendirilir. Daha iyi olacağını söylemiyorum, sadece birinci sınıf nesneler bu işlevleri tamamen engellemez. - gerrit
Üzgünüz, ama "Python’un en büyük WTF’si" en kesinlikle tasarım kusuru. Bu, bir hata kaynağıdır herkes Bir noktada, hiç kimse bu davranışı ilk başta beklemez, yani başlangıçta bu şekilde tasarlanmaması gerektiği anlamına gelir. Onların içinden atlamak zorunda oldukları şeylerin umrumda değil. meli Python'u tasarladı, böylece varsayılan argümanlar durağan değil. - BlueRaja - Danny Pflughoeft
Tasarım kusuru olsun ya da olmasın, cevabınız, bu davranışın bir şekilde gerekli olduğunu, işlevlerin birinci sınıf nesneler olduğu ve doğal olarak böyle olmadığı için doğal ve açık olduğunu ima eder gibi görünmektedir. Python'un kapanmaları var. Varsayılan argümanı, işlevin ilk satırındaki bir ödevle değiştirirseniz, her çağrıyı (potansiyel olarak kapsam içinde beyan edilen adları kullanarak) ifadesini değerlendirir. İşlev her seferinde aynı şekilde çağrıldığında varsayılan argümanların değerlendirilmesinin mümkün veya makul olmayacağına dair hiçbir neden yoktur. - Mark Amery
Tasarım doğrudan takip etmiyor functions are objects. Paradigmanızda, öneri, işlevlerin varsayılan değerlerini öznitelikler yerine özellik olarak uygulamak olacaktır. - bukzor


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

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

def eat(food=fruits):
    ...

Yemek bildirimini gördüğümde, en az şaşırtıcı olan şey, eğer ilk parametre verilmezse, bunun tuple eşit olacağını düşünmektir. ("apples", "bananas", "loganberries")

Ancak, daha sonra kodda ileri sürüldüğü gibi,

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

Daha sonra, varsayılan parametreler işlev bildirimi yerine işlev yürütme işlemine tabi tutulduysa, meyvelerin değiştiğini keşfetmek için (çok kötü bir şekilde) şaşıracaktım. Bu, IMO'yu keşfetmekten daha şaşırtıcı olacaktır. fooYukarıdaki fonksiyon listeyi mutasyona sokuyordu.

Asıl sorun, değişken değişkenlerle yatmaktadır ve tüm dillerin bir ölçüde bu problemi vardır. İşte bir soru: Java'da varsayalım, aşağıdaki koda sahibim:

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ın StringBuffer Haritaya yerleştirildiğinde anahtar mı yoksa anahtarı referans olarak mı saklıyor? Her iki durumda da biri şaşırır; nesneyi çıkarmaya çalışan kişi Map kullandıkları anahtarla aynı olan bir değer kullanmak veya kullandıkları anahtar olsa bile nesnelerini alamıyor gibi görünen kişi, kelimenin tam anlamıyla haritaya yerleştirmek için kullanılan nesnenin aynısıdır. Python, neden değiştirilebilen yerleşik veri türlerinin sözlük tuşları olarak kullanılmasına izin vermediğini).

Örneğiniz, Python'un yeni gelenlerinin şaşkına ve ısırılmaya başlayacağı bir durumdan biridir. Ama şunu söyleyeyim, eğer bunu “düzelttiysek”, o zaman bunun yerine sadece ısırıldıkları farklı bir durum yaratabileceğini ve bunun daha az sezgisel olacağını söyleyebilirim. Ayrıca, değişken değişkenlerle uğraşırken bu her zaman geçerlidir; Her zaman birilerinin hangi kodları yazdıklarına bağlı olarak sezgisel olarak bir ya da ters davranış bekleyebilecekleri durumlarda karşılaşırsınız.

Kişisel olarak Python'un şu anki yaklaşımını seviyorum: fonksiyon tanımlandığında varsayılan fonksiyon argümanları değerlendirilir ve bu nesne her zaman varsayılantır. Boş bir listeyi kullanarak özel bir durum oluşturabileceklerini sanıyorum, ama bu tür bir özel kasa, daha fazla şaşkınlığa neden olacak, geriye doğru uyumsuzluğa değinmeyecek.


231
2017-07-15 18:11



Bence bu bir tartışma meselesi. Küresel bir değişken üzerinde hareket ediyorsunuz. Genel değişkeninizi içeren kodunuzda herhangi bir yerde gerçekleştirilen herhangi bir değerlendirme (doğru olarak) ("yaban mersini", "mango") anlamına gelir. varsayılan parametre sadece başka bir durum gibi olabilir. - Stefano Borini
Aslında, ilk örneğinize katılıyorum sanmıyorum. Başlatıcıyı ilk başta olduğu gibi değiştirmeyi düşündüğümden emin değilim, ama eğer yapsaydım, tanımladığınız gibi davranmasını beklerdim - varsayılan değeri ("blueberries", "mangos"). - Ben Blank
Varsayılan parametre olduğu başka bir durum gibi. Beklenmeyen şey, parametrenin yerel bir değişken değil, global bir değişken olmasıdır. Bunun nedeni, kodun işlev tanımında yürütüldüğünden, çağrı yapılmamasıdır. Bunu bir kez anladıktan sonra, aynı şey sınıflar için geçerli, tamamen açık. - Lennart Regebro
Örnek olarak, parlak değil yanıltıcı buluyorum. Eğer some_random_function() ekliyor fruits atama yerine, davranışını eat()  irade değişiklik. Mevcut harika tasarım için çok fazla. Başka bir yerde başvurulan bir varsayılan argüman kullanırsanız ve daha sonra referansın dışından referansı değiştirirseniz, sorun çıkarırsınız. Gerçek WTF, insanların yeni bir varsayılan argümanı (bir liste hazırlayıcısı veya kurucuya yapılan bir çağrı) tanımlaması ve yine biraz al. - alexis
Sadece açıkça ilan ettin. global ve tuple yeniden atandı - eğer kesinlikle hiçbir şey şaşırtıcı değil eat bundan sonra farklı çalışır. - user3467349


AFAICS kimsenin henüz ilgili bölümünü yayınlamadı belgeleme:

Fonksiyon tanımı yürütüldüğünde varsayılan parametre değerleri değerlendirilir. Bu, ifadenin işlev tanımlandığında bir kez değerlendirildiği ve her bir ç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şebilir bir nesne olduğunu anlamak için önemlidir: işlev nesneyi değiştirirse (ör. Bir öğeye bir listeyi ekleyerek), varsayılan değer değiştirilir. Bu genellikle amaçlanan şey değildir. Bunun bir yolu, Yok'u varsayılan olarak kullanmak ve işlevinin gövdesinde açıkça test etmek [...]


195
2017-07-10 14:50



"Bu genellikle amaçlanan şey değildir" cümleleri ve "bunun bir yolu" tasarım kusurunu belgeledikleri için kokuyorlar. - bukzor
@Matthew: Biliyorum, ama bu tuzağa değmez. Genelde stil kılavuzları ve linterleri koşulsuz olarak varsayılan değerlerini bu nedenle yanlış olarak işaretlersiniz. Aynı şeyi yapmanın açık yolu, işlevin bir niteliğini doldurmaktır (function.data = []) veya daha iyisi, bir nesne yapın. - bukzor
Bukzor: Tuzakların dikkat edilmesi ve belgelenmesi gerekiyor, bu yüzden bu soru iyi ve çok sayıda upvotes aldı. Aynı zamanda, tuzaklar mutlaka kaldırılması gerekmez. Python'a yeni başlayanlar listeyi değiştiren bir işleve kaç değişiklik yaptı ve bu değişiklikler orijinal değişkenin içinde görüldü mi? Yine de, nasıl kullanılacağını anladığınızda, değişken nesne türleri harikadır. Sanırım bu özel tuzak hakkındaki düşüncelere geri dönüyor. - Matthew
"Bu genellikle amaçlanan şey değil" ifadesi, "programcının aslında olmasını istediği" değil "Python'un yapması gereken şey" anlamına gelir. - holdenweb
@oriadam Belki bu konuda bir soru göndermek isteyebilirsiniz. Belki niyetlendiğinden farklı bir şey yaparsın. - glglgl


Python tercümanının iç işleyişi hakkında hiçbir şey bilmiyorum (ve ben de derleyiciler ve tercümanlar konusunda uzman değilim), bu yüzden bir şey öneride bulunamaz veya imkansız bir şey önerirsem beni suçlamayın.

Python nesnelerinin sağlanması şartıyla değişebilir Varsayılan argümanları tasarlarken bunun dikkate alınması gerektiğini düşünüyorum. Bir listeyi başlattığınızda:

a = []

bir tane almayı bekliyorsun yeni tarafından başvurulan liste bir.

A = [] neden

def x(a=[]):

Çağrıda değil, işlev tanımında yeni bir liste oluşturulsun mu? Aynı şey "eğer kullanıcı argümanı sağlamıyorsa örneğini yeni bir liste ve onu arayan tarafından üretilmişmiş gibi kullanın. Bunun yerine belirsiz olduğunu düşünüyorum:

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

kullanıcı, ister misin bir tanımlarken veya yürütürken karşılık gelen datetime değerine varsayılan olarak x? Bu durumda, bir öncekinden de olduğu gibi, aynı davranışı, varsayılan argüman "atama", fonksiyonun ilk talimatıymış gibi (datetime.now () fonksiyon çağrısında çağrılır) gibi tutacağım. Diğer yandan, kullanıcı tanım-zaman haritalamasını istediyse:

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

Biliyorum, biliyorum: bu bir kapanış. Alternatif olarak Python, tanımlama zamanı bağlama işlemini zorlamak için bir anahtar kelime sağlayabilir:

def x(static a=b):

97
2017-07-15 23:21



Yapabilirsiniz: def x (a = Hiçbiri): Ve sonra, bir None ise, a = datetime.datetime.now () ayarlayın. - Anon
Biliyorum, bu, neden yürütme zamanı bağlamayı tercih edeceğimi açıklayan bir örnekti. - Utaal
Bunun için teşekkür ederim. Parmağımı neden bu kadar üzmediğimi anlayamadım. En az bir fuzz ve karışıklık ile güzelce yaptınız. C ++ sistem programlama sisteminden gelen ve kimi zaman "çeviren" dil özelliklerini kullanan biri olarak, bu sahte arkadaş beni sınıfın özniteliklerinde olduğu gibi, büyük zamanın yumuşaklığında tekmeledi. İşlerin neden bu şekilde olduğunu anlıyorum ama yardım edemem ama bundan hoşlanmam, ne kadar olumlu olursa olsun. En azından bu benim deneyimime o kadar aykırı, muhtemelen (umarım) bunu asla unutmayacağım ... - AndreasT
@Pithhon'u yeterince uzun bir kere kullandığınızda, Python'un şeyleri sınıf özniteliklerini olduğu gibi yorumlama mantığının ne kadar mantıklı olduğunu görmeye başlarsınız. Bu sadece C ++ (ve Java gibi dillerin belirli tuhaflıkları ve sınırlamaları nedeniyle) C # ...) içeriği için herhangi bir anlam ifade eder class {} ait olarak yorumlanacak blok örnekleri :) Ama sınıflar birinci sınıf nesneler olduğunda, tabii ki doğal olan, içeriklerini (bellekte) içeriklerini (kod içinde) yansıtmaktır. - Karl Knechtel
Normatif yapı kitabımda tuhaf veya sınırlayıcı değildir. Sakar ve çirkin olabileceğini biliyorum, ama ona bir şeyin "tanımı" diyebilirsiniz. Dinamik diller benim için anarşistlere benziyor: Herkesin özgür olduğuna emin olun, ama birisinin çöpü boşaltması ve yolu döşemesi için bir yapıya ihtiyacınız var. Sanırım yaşlıyım ... :) - AndreasT


Eh, neden kodlar çalıştırıldığında bağlamalar yapılır ve işlev tanımlandığında, işlev tanımı yerine getirilir.

Bunu 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 niteliğidir ve bu nedenle, bir şeyler eklediğinizde, o sınıfın tüm örneklerine eklenir. Sebep tam olarak aynı.

Sadece "Nasıl Çalışıyor", ve işlev durumunda farklı şekilde çalışmasını sağlamak muhtemelen karmaşık olabilir ve sınıf durumunda muhtemelen imkansız olabilir veya en azından nesne örneğini çok yavaşlatırsınız, çünkü sınıf kodunu tutmak zorunda kalırsınız ve nesneler oluşturulduğunda çalıştır.

Evet, beklenmedik. Ama kuruş düştüğünde, Python'un genel olarak nasıl çalıştığıyla tam olarak uyuyor. Aslında, bu iyi bir öğretim yardımcısıdır ve bunun neden olduğunu anladığınızda, pitonu daha iyi anlayacaksınız.

Bu iyi bir Python öğreticisinde öne çıkması gerektiğini söyledi. Çünkü bahsettiğiniz gibi, herkes bu soruna er ya da geç giriyor.


72
2017-07-15 18:54



Her örnek için farklıysa, sınıf niteliği değildir. Sınıf özellikleri CLASS'taki niteliklerdir. Bu yüzden isim. Bu nedenle, tüm örnekler için aynıdır. - Lennart Regebro
Python'un davranışının bir tanımını istemiyordu, mantığını istiyordu. Python'da hiçbir şey sadece "Nasıl Çalışır"; Her şey bir sebepten dolayı yapar. - Glenn Maynard
Ve mantığı ben verdim. - Lennart Regebro
Bu "iyi bir öğretim yardımcısı" demezdim, çünkü öyle değil. - Geo
@Geo: Bunun dışında. Python'da birçok şeyi anlamanıza yardımcı olur. - Lennart Regebro


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

1. Performans

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

Eğer çağrı-zamanı değerlendirmesi kullanılırsa, fonksiyonunuz argüman olmadan her kullanıldığında pahalı fonksiyon çağrılır. Her bir aramada pahalı bir fiyat ödersiniz veya harici olarak değeri manuel olarak önbelleğe almanız, ad alanınızı kirletmeniz ve ayrıntı eklemeniz gerekir.

2. Bağlı parametreleri zorlamak

Yararlı bir hile lambda parametrelerini bağlamaktır. şimdiki lambda oluşturulduğunda bir değişkenin bağlanması. Ö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 listesini döndürür. Davranış değiştiyse, bunun yerine bağlanır i göre arama zamanı i'nin değeri, bu yüzden tüm döndürülen işlevlerin bir listesini alırsınız 9.

Bunu uygulamak için tek yol, i ile daha fazla bir kapatma oluşturmak, yani:

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

3. Gözlemleme

Kodu düşünün:

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

Argümanlar ve varsayılanlar hakkında bilgi alabiliriz. inspect modül, hangi

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

Bu bilgi belge oluşturma, meta programlama, dekoratörler vb. İçin çok yararlıdır.

Şimdi, varsayılanların davranışlarının değiştirilebileceğini varsayalım ki, bunun karşılığı aşağıdaki gibidir:

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

Bununla birlikte, iç gözlemleme yeteneğini kaybettik ve varsayılan argümanları gördük. Hangi. Nesneler inşa edilmediğinden, aslında fonksiyonu çağırmadan onları tutamazız. Yapabileceğimiz en iyi şey, kaynak kodunu saklamak ve bunu bir ip olarak döndürmektir.


50
2017-07-16 10:05



Her biri için bir değer yerine varsayılan argümanı oluşturmak için bir işlev varsa, iç gözlem elde edebilirsiniz. denetleme modülü sadece bu işlevi çağırır. - yairchu
@SilentGhost: Davranışın yeniden yaratılması için değiştirilip değiştirilmediğinden bahsediyorum - bunu bir zamanlar güncel davranış ve neden değiştirilebilen varsayılan sorun var. - Brian
@yairchu: Yapının güvenli olduğu varsayılmaktadır (yani yan etkisi yoktur). İntikamın tartışması yapılmamalıdır. yap Herhangi bir şey, ancak keyfi kodları değerlendirmek bir sonuca sahip olabilir. - Brian
Farklı bir dil tasarımı genellikle sadece farklı şeyler yazmak anlamına gelir. İlk örneğiniz kolayca yazılabilir: _expensive = expensive (); özellikle eğer foo (arg = _expensive) yapamaz tekrar değerlendirilmesini istiyorum. - Glenn Maynard
@Glenn - "dışsal olarak değişkenleri önbelleğe al" ile bahsettiğim şey bu - biraz daha ayrıntılı ve ad alanınızda fazladan değişkenlerle sonuçlanıyorsunuz. - Brian


Python savunmasında 5 puan

  1. Basitlik: Davranış aşağıdaki anlamda basittir: Çoğu insan bu tuzağa sadece birkaç kez girer, birkaç kez değil.

  2. Tutarlılık: Python her zaman Nesneleri geçirir, isimleri değil. Varsayılan parametre, belli ki, fonksiyonun bir parçasıdır başlık (işlev gövdesi değil). Bu nedenle değerlendirilmeli Modül yükleme süresinde (ve sadece iç içe geçmediği sürece sadece modül yükleme süresinde) işlev çağrısı zamanında.

  3. kullanışlılık: Frederik Lundh açıklamasında belirttiği gibi arasında "Python'da Varsayılan Parametre Değerleri", Mevcut davranış ileri programlama için oldukça yararlı olabilir. (Sparingly kullanın.)

  4. Yeterli dokümantasyon: En basit Python belgelerinde, öğretici, sorun olarak yüksek sesle bildirildi bir "Önemli uyarı" içinde ilk Bölümün alt bölümü "Fonksiyonların Tanımlanması Hakkında Daha Fazla Bilgi". Uyarı bile kalın yazı kullanır Başlıkların dışında nadiren uygulanır. RTFM: İyi kılavuzu okuyun.

  5. Meta-öğrenme: Tuzağa düşmek aslında çok yararlı an (en azından yansıtıcı bir öğrencisiyseniz), çünkü daha sonra noktayı daha iyi anlayacaksınız Yukarıdaki "Tutarlılık" ve bu Python hakkında çok şey öğretirim.


47
2018-03-30 11:18



Bu davranışın üretimdeki kodumu karıştırması bir yıl sürdü, bu tasarım kusuruna rastgele girinceye kadar tam bir özelliği kaldırarak sona erdi. Django kullanıyorum. Evreleme ortamının pek çok isteği olmadığından, bu hatanın Kalite Güvencesi üzerinde hiçbir etkisi olmadı. Yaşadığımız ve birçok eşzamanlı istek aldığımız zaman - bazı yardımcı fonksiyonlar birbirinin parametrelerinin üzerine yazmaya başladı! Güvenlik delikleri, hatalar ve neyin yapılmaması. - oriadam
@oriadam, suç yok, ama Python'u daha önce hiç böyle çalışmadan nasıl öğrendiğini merak ediyorum. Şimdi sadece Python'u öğreniyorum ve bu olası tuzak resmi Python öğreticisinde bahsedilen varsayılan argümanların ilk bahsinin yanı sıra. (Bu cevabın 4. noktasında belirtildiği gibi). Ahlâkî olanın –hiçbirşekilde değil– resmi dokümanlar Üretim yazılımı oluşturmak için kullandığınız dilin. - Wildcard
Ayrıca, yaptığım işlev çağrısına ek olarak bilinmeyen bir karmaşıklık işlevi çağrılırsa (bana) şaşırtıcı olurdu. - Vatine


Neden neden görüşmek istemiyorsun?

Ben Gerçekten mi Hiç kimse Python tarafından sunulan içgörülü iç gözlemi gerçekleştiremediğini şaşırttı (2 ve 3 Uygula)

Basit bir küçük işlev verildiğinde func olarak tanımlandı:

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

Python karşılaşınca, ilk yapacağı şey, code Bu işlev için nesne. Bu derleme adımı yapılırken, piton değerlendirir* ve sonra depolar varsayılan argümanlar (boş liste [] burada) işlev nesnesinin kendisi. Bahsedilen en önemli cevap: liste a şimdi bir düşünülebilir üye işlev func.

Öyleyse, listenin nasıl genişlediğini incelemek için önce ve sonra biraz iç gözlem yapalım. içeride işlev nesnesi. kullanıyorum Python 3.x Bunun için, Python 2 için de geçerlidir (kullanım __defaults__ veya func_defaults Python 2'de; evet, aynı şey için iki isim.

Yürütme Öncesi İşlev:

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

Python bu tanımlamayı gerçekleştirdikten sonra, belirtilen herhangi bir varsayılan parametreyi alır (a = [] burada ve onları cram __defaults__ işlev nesnesi için öznitelik (ilgili bölüm: Callables):

>>> func.__defaults__
([],)

O.k, yani tek giriş olarak boş bir liste __defaults__, beklendiği gibi.

Yürütme Sonrası İşlev:

Şimdi bu işlevi yürütelim:

>>> func()

Şimdi, onları görelim __defaults__ tekrar:

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

Şaşkın? Nesnenin içindeki değer değişir! Fonksiyona ardışık çağrılar artık sadece gömülü olana ekleyecektir list nesne:

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

Yani, işte bunun nedeni, bunun nedeni 'Kusur' olur, çünkü varsayılan argümanlar fonksiyon nesnesinin bir parçasıdır. Burada garip bir şey yok, her şey biraz şaşırtıcı.

Bu mücadele için ortak çözüm her zamanki None varsayılan olarak ve sonra işlev gövdesinde başlatılır:

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

Fonksiyon gövdesi her defasında yeniden çalıştırıldığı için, hiçbir argüman geçilmediyse her zaman yeni ve boş bir liste alırsınız. a.


Listede daha fazla doğrulamak için __defaults__ işlevde kullanılanla aynıdır func işlevini değiştirmek için sadece değiştirebilirsiniz id listenin a fonksiyon gövdesinin içinde kullanılır. Ardından, listedeki listeyle karşılaştırın. __defaults__ (pozisyon [0] içinde __defaults__) ve bunların gerçekten aynı liste örneğine nasıl başvurduğunu göreceksiniz:

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

Tüm iç gözlem gücü ile!


* Python'un işlevi derleme sırasında varsayılan argümanları değerlendirdiğini doğrulamak için, aşağıdakileri uygulamayı deneyin:

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

fark edeceğiniz gibi input() işlevi oluşturma ve isme bağlama işleminden önce çağrılır bar yapılmış.


43
2017-12-09 07:13



mı id(...) Bu son doğrulama için gerekliydi veya is operatör aynı soruyu cevaplıyor mu? - das-g
@ Das-g is sadece iyi yapardım, sadece kullandım id(val) çünkü daha sezgisel olabileceğini düşünüyorum. - Jim Fasarakis Hilliard
@JimFasarakisHilliard Ben bir istemi eklemek istiyorum input. sevmek input('Did you just see me without calling me?'). Bu daha net imo yapar. - Ev. Kounis
@ Ev.Kounis Hoşlanıyorum! Bunu işaret ettiğin için teşekkürler. - Jim Fasarakis Hilliard


Bu davranış, tarafından kolay açıklanır:

  1. fonksiyon (sınıf vb.) beyanı sadece bir kez yapılır, tüm varsayılan değer nesneleri oluşturulur
  2. her şey referans olarak geçmiştir

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

40
2017-07-15 19:15



# 4'niz insanlara kafa karıştırıcı olabilir, çünkü tamsayılar değişmez ve "if" doğru değildir. Örneğin d, 0 olarak ayarlandığında, d .__ add __ (1) 1 değerini döndürür, ancak d yine de 0 olur. - Anon
(Aslında, eklemek kötü bir örnektir, ancak değişmez olan tamsayılar benim ana noktamdır.) - Anon
evet, bu iyi bir örnek değildi - ymv
Bunu kontrol etmeyi kontrol ettikten sonra, [b] 'ye b]' yi ekleyin, __ ([1]) 'i döndürün. Benim hatam. - Anon
@ANon: var __iadd__ama int ile çalışmıyor. Tabii ki. :-) - Veky


Sorduğun şey şu:

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

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

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örmezden gelemeyeceğimiz, açık bir şekilde func (None, None) çağrılması durumu hariç.

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

Bir cevap muhtemelen buradadır - her işlevi varsayılan parametrelerle bir kapanmaya dönüştürür. Tercümana tamamen gizlenmiş olsa bile ve tam bir kapanma olmasa bile, veriler bir yerlerde saklanmalıdır. Daha yavaş olur ve daha fazla bellek kullanır.


30
2017-07-15 20:18



Bir kapanışa gerek duymayacaktı - bunu düşünmenin daha iyi bir yolu, sadece kodun ilk satırını varsayılan olarak üreten bytecode'unu yapmaktır - her ne kadar olursa olsun bu noktada bedeni derlediyseniz - kod arasında gerçek bir fark yoktur vücutta argümanlar ve kod. - Brian
Doğru, ama yine de Python'u yavaşlatırdı, ve sınıf tanımları için aynısını yapmazsanız, gerçekten de şaşırtıcı olurdu, ki bu, her defasında bir örnek oluşturmak için bütün sınıf tanımını yeniden çalıştırmanız gerektiğinden aptalca yavaşlatacaktır. sınıf. Bahsedildiği gibi, sorun, problemden daha şaşırtıcı olacaktır. - Lennart Regebro
Lennart ile anlaştı. Guido'nun söylediği gibi, her dil özelliği veya standart kütüphane için birisi orada kullanarak. - Jason Baker
Şimdi değiştirmek delilik olurdu - sadece neden böyle olduğunu araştırıyoruz. Başlangıçta gecikmeli değerlendirme yaptıysa, mutlaka şaşırtıcı olmazdı. Böyle bir çekirdeğin ayrıştırma farklılığının, bir bütün olarak dil üzerinde etkili ve muhtemelen pek çok belirsizliğe sahip olacağı kesinlikle doğrudur. - Glenn Maynard


1) "Mutable Default Argümanı" olarak adlandırılan sorun genel olarak aşağıdakileri gösteren özel bir örnektir:
"Bu problemle tüm fonksiyonlar gerçek parametrede benzer yan etki probleminden de muzdarip"
Bu genellikle programlanamayan kurallara aykırıdır ve ikisi birlikte sabitlenmelidir.

Örnek:

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: a kopya
Kesinlikle güvenli bir çözümdür copy veya deepcopy önce giriş nesnesini ve daha sonra kopya ile ne yaparsanız yapı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şken tür gibi bir kopya yöntemine sahiptir some_dict.copy() veya some_set.copy()veya kolayca kopyalanabilir somelist[:] veya list(some_list). Her nesne tarafından da kopyalanabilir copy.copy(any_object) veya daha fazla copy.deepcopy() (ikinci nesne, değişebilen nesne değişebilir nesnelerden oluşuyorsa faydalıdır). Bazı nesneler temel olarak "dosya" nesnesi gibi yan etkilere dayanır ve kopya ile anlamlı şekilde çoğaltılamaz. kopyalama

İçin örnek problem Benzer bir soru

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]

Hiç saklanmamalı halka açık Bu işlev tarafından döndürülen bir örneğin özniteliği. (Varsayalım ki özel Örnek nitelikleri, bu sınıfın veya alt sınıfların dışından kongre yoluyla değiştirilmemelidir. diğer bir deyişle _var1 özel bir özelliktir)

Sonuç:
Girdi parametrelerinin nesneleri değiştirilmemeli (mutasyona uğramış) veya işlev tarafından döndürülen bir nesneye bağlanmamalıdırlar. (Önceden tavsiye edilen yan etkileri olmadan programlamayı tercih edersiniz. Wiki "yan etki" hakkında (İlk iki paragraf bu bağlamda geçerlidir.) .)

2)
Sadece asıl parametrenin yan etkisi gerekliyse ancak varsayılan parametrede istenmeyen ise o zaman yararlı çözüm def ...(var1=None):  if var1 is None:  var1 = []  Daha..

3) Bazı durumlarda varsayılan parametrelerin değişken davranışı yararlı.


29
2017-11-22 18:09



Umarım Python'un farkındasınızdır. değil işlevsel bir programlama dili. - Veky
Evet, Python, bazı işlevsel özelliklerle çoklu paragigm dilidir. (“Her problemi çivi gibi göstermeyin, çünkü bir çekiçiniz var.”) Birçoğu Python'un en iyi uygulamalarında. Python bir ilginç var NASIL İşlevsel Programlama Diğer özellikler burada belirtilmeyen, kapanma ve körelme. - hynekcer
Ayrıca, bu son aşamada, Python'un ödev semantiğinin, gerektiğinde veri kopyalanmasını önlemek için açıkça tasarlandığını, böylece kopyaların (ve özellikle de derin kopyaların) oluşturulmasının hem çalışma zamanı hem de bellek kullanımını olumsuz etkileyeceğini de eklemeliyim. Bu nedenle, sadece gerektiğinde kullanılmalıdır, ancak yeni gelenler çoğu zaman, ne zaman olduğunu anlamakta güçlük çekerler. - holdenweb
@holdenweb Katılıyorum. Geçici bir kopya en yaygın yoldur ve bazen orijinal değiştirilebilen verileri potansiyel olarak değiştiren yabancı bir işlevden korumanın tek olası yoludur. Neyse ki, verileri makul olmayan şekilde değiştiren bir işlev, bir hata olarak kabul edilir ve bu nedenle yaygın değildir. - hynekcer
Bu cevaba katılıyorum. Ve neden anlamıyorum def f( a = None ) Gerçekten başka bir şey ifade ettiğinde yapı önerilmektedir. Kopyalama tamam, çünkü argümanları değiştirmemelisiniz. Ve ne zaman yaparsın if a is None: a = [1, 2, 3], yine de listeyi kopyaladın. - koddo