it-swarm.dev

"En Küçük Şaşırtma" ve Değişken Varsayılan Argüman

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

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

Python acemileri bu işlevin her zaman yalnızca bir element içeren bir liste döndürmesini bekler: [5]. Sonuç bunun yerine çok farklı ve çok şaşırtıcı (acemiler için):

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

Bir zamanlar bir menajerim bu özellikle ilk karşılaşmasını yaptı ve bunu dilin "dramatik tasarım kusuru" olarak nitelendirdi. Davranışın altında yatan bir açıklaması olduğunu ve içselleri anlamadığınızda gerçekten çok şaşırtıcı ve beklenmedik olduğunu söyledim. Ancak, şu soruyu kendime cevaplayamadım (kendime): Fonksiyon tanımında varsayılan fonksiyonun bağlanmasının sebebi nedir? Fonksiyonun yürütülmesinde değil. Tecrübeli davranışın pratik bir kullanımı olduğundan şüpheliyim (C de statik değişkenleri üreyen böcekler olmadan kim kullanıyor?)

Düzenle :

Baczek ilginç bir örnek yaptı. Özellikle yorumlarınızın ve Utaal'larınızın çoğu ile birlikte, daha fazla ayrıntı verdim:

>>> 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ının parametrelerin kapsamını nereye koyacağına bağlı olduğu görülüyor: fonksiyonun içinde mi, yoksa onunla birlikte mi?

İşlev içinde bağlamanın yapılması, işlev tanımlandığında, derin bir kusur oluşturacak bir şey olarak adlandırılırsa, x öğesinin etkin olarak belirtilen varsayılan değere bağlı olduğu anlamına gelir: def satırı, bağlamanın bir parçası olarak "karma" olur (işlev nesnesinin) tanımında ve işlev çağırma zamanında bir kısmı (varsayılan parametrelerin atanması) olur.

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

2310
Stefano Borini

Aslında, bu bir tasarım hatası değildir ve içsellerden veya performanstan dolayı değildir.
Basitçe Python'daki fonksiyonların birinci sınıf nesneler olduğu ve sadece bir kod parçası olduğu gerçeğinden geliyor.

Bu şekilde düşünmeye başlar başlamaz, o zaman tamamen mantıklı geliyor: bir işlev tanımı üzerinde değerlendirilen bir nesnedir; varsayılan parametreler "üye verisi" türündendir ve bu nedenle durumları bir çağrıdan diğerine değişebilir - aynen diğer nesnelerde olduğu gibi.

Her durumda, Effbot bu davranışın nedenlerini Python'daki Varsayılan Parametre Değerleri içinde çok güzel bir şekilde açıklamaktadır.
Çok net buldum ve fonksiyon nesnelerinin nasıl çalıştığı hakkında daha iyi bilgi edinmek için onu okumayı öneriyorum.

1486
rob

Aşağıdaki kodun sizde olduğunu varsayalım

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

def eat(food=fruits):
    ...

Yemeğin ilanını gördüğümde, en az şaşırtıcı şey, ilk parametre verilmezse, bunun Tuple ("apples", "bananas", "loganberries") ile eşit olacağını düşünmektir.

Ancak, daha sonra kodunda sanıldığı gibi, ben bir şey yapmak

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

o zaman varsayılan parametreler işlev bildirimi yerine işlev yürütme işlemine bağlıysa, meyvelerin değiştirildiğini keşfetmek için çok şaşırırdım (çok kötü bir şekilde). Bu, yukarıdaki foo işlevinizin listeyi değiştirmekte olduğunu keşfetmekten daha şaşırtıcı bir IMO olurdu.

Asıl sorun değişken değişkenlere dayanıyor ve tüm dillerin bir dereceye kadar bu sorunu var. İşte bir soru: Java ile varsayalım ki aşağıdaki 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?

Şimdi, haritam haritaya yerleştirildiğinde StringBuffer anahtarının değerini kullanıyor mu, yoksa anahtarı referansa göre saklıyor mu? Her iki durumda da, birileri şaşırmış; Ya nesneyi Map öğesinden çıkarmaya çalışan kişi, içine koydukları ile aynı değeri kullanarak ya da kullandıkları anahtar kelimenin tam anlamıyla aynı olmasına rağmen, nesnelerini alamıyor gibi görünüyor. haritaya koymak için kullanılan nesne (Python'un değişken veri türlerinin sözlük anahtarları olarak kullanılmasına izin vermemesinin nedeni budur).

Örnek, Python'un yeni gelenlerin şaşırıp ısırdığı bir durumdan iyi bir tanesi. Ancak, eğer bunu "düzeltirsek", o zaman bunun sadece ısırıldıkları yerde farklı bir durum yaratacağını ve bunun daha az sezgisel olacağını savunuyorum. Dahası, değişken değişkenlerle uğraşırken bu her zaman böyledir; her zaman hangi kodun yazdığına bağlı olarak birisinin sezgisel olarak bir veya tam tersi bir davranış bekleyebileceği durumlarla karşılaşırsınız.

Şahsen Python'un şu anki yaklaşımını beğeniyorum: varsayılan işlev argümanları işlev tanımlandığında ve bu nesne her zaman varsayılan olduğunda değerlendirilir. Sanırım boş bir liste kullanarak özel dava açabilirlerdi, ancak bu tür özel kaplamalar geriye dönük uyumsuzluktan bahsetmemekle birlikte daha fazla şaşkınlığa neden olacaktı.

255
Eli Courtwright

AFAICS henüz hiç kimse belgeleme 'nin ilgili bölümünü göndermedi:

Varsayılan parametre değerleri, işlev tanımı yürütüldüğünde değerlendirilir. Bu, fonksiyon 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, varsayılan bir parametrenin bir liste veya sözlük gibi değişken bir nesne olduğunu anlamak için özellikle önemlidir: işlev nesneyi değiştirirse (örneğin, listeye bir öğe ekleyerek), varsayılan değer etkindir. Bu genellikle amaçlanan şey değil. Bunun bir yolu, varsayılan olarak Yok seçeneğini kullanmak ve işlev gövdesinde açıkça sınamaktır [...]

223
glglgl

Python tercümanı iç işleri hakkında hiçbir şey bilmiyorum (ve ayrıca derleyiciler ve tercümanlar konusunda da uzman değilim) bu yüzden, hassas veya imkansız bir şey teklif edersem beni suçlama.

Python nesnelerinin değişken olması koşuluyla varsayılan argümanların tasarımını yaparken bunun dikkate alınması gerektiğini düşünüyorum. Bir listeyi başlattığınızda:

a = []

a tarafından başvurulan bir yeni liste almayı bekliyorsunuz.

Neden a=[] in

def x(a=[]):

işlev tanımı üzerinde yeni bir liste başlatılsın mı? Tıpkı "Kullanıcı argümanı sağlamıyorsa instantiate a yeni bir liste yazıp, arayanlar tarafından oluşturuluyormuş gibi kullanıp kullanmadığını" sormak gibi bir şey. Bunun yerine belirsiz olduğunu düşünüyorum:

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

user, a öğesinin x öğesini tanımlarken veya çalıştırırken karşılık gelen tarih saatine varsayılan olmasını ister misiniz? Bu durumda, öncekinde olduğu gibi, "atama" varsayılan argümanının fonksiyonun ilk talimatı olduğu gibi aynı davranışı yapacağım (datetime.now(), fonksiyon çağırmada çağrılır). Öte yandan, kullanıcı tanım-zaman eşlemesini isterse yazabileceği:

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

Biliyorum, biliyorum: bu bir kapanış. Alternatif olarak, Python, tanım-zaman bağlamasını zorlamak için bir anahtar kelime sağlayabilir:

def x(static a=b):
107
Utaal

Bunun nedeni, basitçe, kod çalıştırıldığında ciltlemelerin yapılması ve işlev tanımının yürütülmesidir, işlevler tanımlandığında….

Bunu karşılaştırın:

class BananaBunch:
    bananas = []

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

Bu kod aynı beklenmeyen durumdan muzdariptir. muzlar bir sınıf niteliğidir ve dolayısıyla ona bir şeyler eklediğinizde, o sınıfın tüm örneklerine eklenir. Sebep tamamen aynı.

Bu sadece "Nasıl Çalışır" dır ve işlev durumunda farklı şekilde çalışmasını sağlamak, muhtemelen karmaşık olacaktır ve sınıf durumunda, sınıf kodunu etrafta tutmanız gerektiği gibi, imkansız ya da en azından nesne başlatmayı çok yavaşlatır. ve nesneler oluşturulduğunda onu yürütün.

Evet, beklenmedik bir durum. Ancak bir kez kuruş düştüğünde, Python'un genel olarak nasıl çalıştığına mükemmel şekilde uyar. Aslında, bu iyi bir öğretim yardımı ve bunun neden olduğunu bir kez anladıktan sonra pitonu çok daha iyi yakalayacaksınız.

Bu, herhangi bir Python öğreticisinde belirgin bir şekilde öne çıkması gerektiğini söyledi. Çünkü bahsettiğiniz gibi, herkes bu soruna er ya da geç karşılaşır.

78
Lennart Regebro

Çalışma zamanında nesneleri yaratmanın daha iyi bir yaklaşım olacağını düşünürdüm. Artık daha az eminim, çünkü bazı faydalı özellikleri kaybedersiniz, ancak yeni başlayanların kafa karışıklığını önlemek için ne olursa olsun buna değer olabilir. Bunu yapmanın dezavantajları:

1. Performans

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

Eğer çağrı zamanı değerlendirmesi kullanılıyorsa, fonksiyonunuz her argüman olmadan kullanıldığında pahalı fonksiyon çağrılır. Her aramada pahalı bir fiyat ödersiniz veya değeri harici olarak manuel olarak önbelleğe almak, ad alanınızı kirletmek ve ayrıntılarını eklemek zorunda kalırsınız.

2. Zorlama bağlı parametreler

Yararlı bir püf noktası, bir lambda'nın parametrelerini, lambda oluşturulduğunda bir değişkenin akım 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şirse, bunun yerine i öğesini i'nin call-time değerine bağlar, böylece tüm 9 işlevini döndüren işlevlerin bir listesini alırsınız.

Aksi takdirde bunu gerçekleştirmenin tek yolu i sınırına, yani:

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

3. Gözden Geçirme

Kodu düşünün:

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

Argümanlar ve varsayılanlar hakkında inspect modülünü kullanarak bilgi alabiliriz.

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

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

Şimdi, varsayılanların davranışının, eşdeğerde olacağı şekilde değiştirilebileceğini varsayalım:

_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, inceleme kabiliyetini kaybettik ve varsayılan değişkenlerin is ne olduğunu gördük. Nesneler inşa edilmediği için, fonksiyonu gerçekten çağırmadan hiçbir zaman onları alamayız. Yapabileceğimizin en iyisi kaynak kodunu saklamak ve onu bir dize olarak döndürmektir.

56
Brian

Python savunmasında 5 puan

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

  2. Tutarlılık: Python her zaman/ isimleri değil nesneleri geçirir.) Varsayılan parametre, açık bir şekilde, işlev başlığının bir parçasıdır (işlev gövdesinin değil). Bu nedenle modül yükleme zamanında değerlendirilmelidir (ve yalnızca modül yükleme zamanında, iç içe geçmedikçe), işlev çağrısı sırasında değil.

  3. Yararlılık: Frederik Lundh, "Python'daki Varsayılan Parametre Değerleri" açıklamasında belirttiğine göre, mevcut davranış, gelişmiş programlama için oldukça faydalı olabilir. (Az miktarda kullanın.)

  4. Yeterli belgeler: En temel Python belgelerinde, öğretici, konu yüksek sesle "Önemli uyarı" olarak ilkBölüm "İşlev Tanımlama Hakkında Daha Fazla Bilgi") olarak açıklandı. Bu uyarı, başlıkların dışında nadiren uygulanan kalın bir yazı bile kullanır .. RTFM: Kullanım kılavuzunu okuyun.

  5. Meta-öğrenme: Tuzağa düşmek aslında çok yararlı bir andır (en azından yansıtıcı bir öğreniciyseniz), çünkü daha sonra yukarıdaki "Tutarlılık" noktasını daha iyi anlayacaksınız ve bu size çok şey öğretecektir. Python.

54
Lutz Prechelt

Neden içten içe bakmıyorsun?

Ben gerçekten kimsenin, Python (2 ve 3 uygulanabilir) tarafından çağrılan callables'da bulunan içgörülü bir görüşme yapmadığına şaşırdım.

Basit bir küçük işlev verilen func, şöyle tanımlanır:

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

Python karşılaştığında, bu işlev için bir code nesnesi oluşturmak için yapacağı ilk şey onu derlemek. Bu derleme adımı yapılırken Python * değerlendirir ve sonra varsayılan argümanları (burada boş bir liste [] burada) işlev nesnesinin içinde saklar. Bahsedilen en üst cevabın verdiği gibi: a listesi artık func işlevinin bir üyesi olarak kabul edilebilir.

Öyleyse, listenin nasıl genişletildiğini incelemeye başlamadan önce ve sonra, bazı fonksiyonlarını inceleyelim: fonksiyon nesnesinin içinde. Bunun için Python 3.x kullanıyorum, Python 2 için de aynısı geçerli (Python 2'de __defaults__ veya func_defaults kullanın; evet, aynı şey için iki ad kullanın).

Yürütmeden Önce İşlev:

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

Python bu tanımı uyguladıktan sonra belirtilen herhangi bir varsayılan parametreyi alır (burada a = [] burada) ve işlev nesnesi için __defaults__ niteliğinde sıkıştırır (ilgili bölüm: Callables):

>>> func.__defaults__
([],)

Tamam, beklediğiniz gibi __defaults__ içindeki tek giriş olarak boş bir liste.

Yürütmeden Sonra İşlev:

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

>>> func()

Şimdi tekrar bu __defaults__ dosyasını görelim:

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

Şaşkın? Nesnenin içindeki değer değişir! Fonksiyona yapılan ardışık çağrılar şimdi basitçe şu gömülü list nesnesine eklenecektir:

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

Öyleyse işte orada, bunun 'kusur' olmasının nedeni, varsayılan argümanların fonksiyon nesnesinin bir parçası olmasıdır. Burada olan garip bir şey yok, hepsi biraz şaşırtıcı.

Bununla mücadele etmek için ortak çözüm varsayılan olarak None kullanmak ve sonra 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 bir kez daha yürütüldüğünden, a için bir argüman iletilmezse, her zaman yeni ve boş bir liste alırsınız.


__defaults__'daki listenin func işlevinde kullanılan ile aynı olduğunu doğrulamak için, işlev gövdesinde kullanılan id listesinin a işlevini döndürmek için işlevinizi değiştirebilirsiniz. Ardından, __defaults__'daki ([0] konumundaki __defaults__ konumu) listeyle karşılaştırın; bunların aslında aynı liste örneğine gönderme yaptığını 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 fonksiyonun derlenmesi sırasındaki varsayılan argümanları 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

farkedeceğiniz üzere, input() işlevini oluşturma ve bar ismine bağlama işleminden önce çağrılır.

Bu davranış kolay açıklanır:

  1. işlev (sınıf vb.) bildirimi yalnızca bir kez yürütülür ve tüm varsayılan değer nesneleri oluşturulur.
  2. her şey referans tarafından geçildi

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 yaratır - yeni nesne yazdırılır
  2. b değişmez - yeni dizi varsayılan değerden derlenir ve yazdırılır
  3. c change - işlem aynı nesnede gerçekleştirilir - ve yazdırılır
46
ymv

İstediğiniz şey şunun nedeni:

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

buna dahili olarak 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()

açıkça göz ardı edeceğimiz func (Yok, Yok) işlevini çağırmak dışında.

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ğerlendiriyorsunuz?

Tek bir cevap muhtemelen tam orada - varsayılan parametreleri olan her fonksiyonu etkin bir şekilde kapatacak. Tamamen tercüman içinde gizlenmiş ve tamamen kapalı bir kapak olmasa bile, verilerin bir yerde saklanması gerekir. Daha yavaş olurdu ve daha fazla hafıza kullanırdı.

33
Glenn Maynard

1) "Değişken Varsayılan Argüman" olarak adlandırılan sorun genel olarak şunu gösteren özel bir örnektir:
"Bu sorunla ilgili tüm işlevler, asıl parametre 'de benzer yan etki sorununa sahip"
Bu, işlevsel programlama kurallarına aykırıdır, genellikle dikkate alınmaz ve her 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üm, öncecopyveyadeepcopyönce girdi nesnesine, sonra da kopya ile ne olursa olsun yapmaktır.

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

Yerleşik değişken tiplerin çoğu, some_dict.copy() veya some_set.copy() gibi bir kopyalama yöntemine sahiptir veya somelist[:] veya list(some_list) gibi kolayca kopyalanabilir. Her nesne copy.copy(any_object) veya daha fazla copy.deepcopy() tarafından daha da kopyalanabilir (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 kopya ile anlamlı bir şekilde çoğaltılamaz. kopyalama

İçin örnek problem benzer bir SO sorusu

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 hiçbir genel özelliğine kaydedilmemelidir. (Örneğin, private özniteliklerinin bu sınıfın dışından veya alt sınıfların kurallara göre değiştirilmemesi gerektiğini varsayalım. Yani, _var1 özel bir özniteliktir)

Sonuç:
Giriş parametreleri nesneleri yerinde değiştirilmemeli (mutasyona uğratılmamış) ya da işlev tarafından döndürülen nesnelere bağlanmamalıdır. (Şiddetle tavsiye edilen yan etkiler olmadan programlamayı tercih edersek. Bkz. "yan etki" ile ilgili Wiki (İlk iki paragraf bu bağlamda geçerlidir.).)

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

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

31
hynekcer

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

>>> 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üşte varsayılan değer yok, ancak tamamen aynı sorunu yaşarsınız.

Sorun şu ki fooname__, modifiye arayan bunu beklemiyorsa, arayandan geçen değişken bir değişkendir. Eğer fonksiyon append_5; daha sonra arayan kişi, girdiği değeri değiştirmek için fonksiyonu çağırır ve davranış beklenir. Ancak, böyle bir fonksiyonun varsayılan bir argüman alma olasılığı çok düşük olacaktır ve muhtemelen listeyi geri döndürmez (arayan kişi zaten o listeye bir referansa sahip olduğundan; henüz girdiği bir listeye).

Özgün fooname__, varsayılan bir argümanla, açıkça girilmiş veya varsayılan değere sahip olsun, aname__'yi değiştirmemelidir. Kodunuz, bağımsız değişkenlerin değiştirilmeleri gerektiği bağlamından/adından/belgelerinden açıkça anlaşılmadıkça, yalnızca değişken argümanları bırakmalıdır. Bağımsız değişkenler olarak yerel değişkenler olarak iletilen değişken değerleri kullanmak, Python'da olup olmadığımız ve dahil olan varsayılan argümanların olup olmadığı son derece kötü bir fikirdir.

Bir şeyi hesaplama sürecinde yerel bir geçici olanı tahrip edici şekilde manipüle etmeniz ve manipülasyonunuzu bir argüman değerinden başlatmanız gerekirse, bir kopya çıkarmanız gerekir.

28
Ben

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 (bakınız 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 üreyen böcekler olmadan kim kullanıyor?)

Gördüğünüz gibi, değişmez varsayılan argümanlar kullanılırken, bir performans avantajı vardır. Sık kullanılan bir işlev veya varsayılan argümanın oluşturulması uzun zaman alıyorsa, bu bir fark yaratabilir. Ayrıca, Python'un C olmadığını unutmayın. C'de hemen hemen ücretsiz olan sabitleriniz var. Python'da bu avantajdan yararlanamazsınız.

25
Jason Baker

Zaten meşgul olan bir konu, ancak burada okuduklarımdan, aşağıdakiler dahili olarak nasıl çalıştığını fark etmeme 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
Stéphane

En kısa cevap muhtemelen "tanım uygulamadır" olacaktır, bu nedenle tüm argüman hiçbir anlam ifade etmemektedir. Daha ayrıntılı bir örnek olarak, şunu söyleyebilirsin:

def a(): return []

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

Umarım, def ifadesinin yürütme sırasındaki varsayılan argüman ifadelerinin yürütülmemesinin kolay olmadığını veya bir anlam ifade etmediğini veya her ikisini de göstermesi yeterlidir.

Yine de, varsayılan kurucuları kullanmaya çalıştığınızda bunun bir sonuç olduğunu kabul ediyorum.

22
Baczek

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

Varsayılan argümanlar, fonksiyonun bir fonksiyon nesnesine derlendiği sırada değerlendirilir. İşlev tarafından kullanıldığında, bu işlev tarafından birden çok kez, aynı nesne kalır ve kalırlar.

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

Mutasyona uğrarlar çünkü her seferinde aynı nesnelerdir.

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

buna neredeyse tamamen 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 tanıtım - her başvuruda bulunduklarında aynı nesne olduklarını doğrulayabilirsiniz

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

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 __== '__main__':
    print('5. calling example_function twice!:')
    example_function()
    example_function()

ve python example.py ile çalıştırma:

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 Küçük Şaşkınlık" ilkesini ihlal ediyor mu?

Bu uygulama sırası yeni Python kullanıcıları için kafa karıştırıcıdır. Python yürütme modelini anlarsanız, oldukça beklenir.

Yeni Python kullanıcılarına verilen genel talimat:

Ancak bu nedenle, yeni kullanıcılara verilen olağan 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, fonksiyona varsayılandan başka bir argüman alıp almadığımızı bildirmek için Yok tekilini sentinel nesnesi olarak kullanır. Eğer argüman alamazsak, o zaman varsayılan olarak [] adlı boş bir liste kullanmak istiyoruz.

Kontrol akışının öğretici bölümünün dediği gibi :

Varsayılan çağrının sonraki çağrılar arasında paylaşılmasını istemiyorsanız, bunun yerine işlevi şöyle yazabilirsiniz:

def f(a, L=None):
    if L is None:
        L = []
    L.append(a)
    return L
20
Aaron Hall

Yok kullanarak basit bir geçici çö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
hugo24

Aşağıdakileri göz önünde bulundurursanız, bu davranış şaşırtıcı değildir:

  1. Salt okunur sınıf davranışı, atama denemelerinde ortaya çıkar ve
  2. İşlevler nesnelerdir (kabul edilen cevapta iyi açıklanmıştır).

(2) rolü bu başlıkta geniş ölçüde ele alınmıştır. (1) muhtemelen bu faktör diğer dillerden geldiğinde "sezgisel" olmadığı için faktöre neden olan şaşkınlıktır.

(1) Python'da açıklanmıştır sınıflardaki öğretici . Salt okunur bir sınıf özelliğine bir değer atama girişiminde bulunmak için:

... en içteki kapsam dışında bulunan tüm değişkenler salt okunurdur (böyle bir değişkene yazma denemesi, en içteki kapsamda aynı şekilde adlandırılmış dış değişkeni değiştirmeden bırakarak basit bir şekilde içteki kapsamda yeni bir yerel değişken oluşturur).

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

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

Burada foo bir nesnedir ve a, foo'ın bir özelliğidir (foo.func_defs[0] adresinde bulunur). a bir liste olduğundan, a değişkendir ve bu nedenle foo'nin okuma-yazma özelliğidir. İşlev başlatıldığında imzanın belirttiği şekilde boş listeye ilklendirilir ve işlev nesnesi olduğu sürece okuma ve yazma için kullanılabilir.

Bir varsayılanı geçersiz kılmadan foo öğesini çağırmak, bu varsayılanın değerini foo.func_defs öğesinden kullanır. Bu durumda, foo.func_defs[0], işlev nesnesinin kod kapsamı dahilindeki a için kullanılır. a dosyasındaki değişiklikler, foo nesnesinin bir parçası olan ve foo içindeki kodun çalıştırılması arasında devam eden foo.func_defs[0] öğesini değiştirir.

Şimdi, bunu aşağıdaki örneklerle karşılaştırın diğer dillerin varsayılan argüman davranışını taklit ederek işlev imzası varsayılanları, işlev her yürütüldüğünde kullanılacak şekilde:

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

(1) ve (2) hesaba katıldığında, bunun istenen davranışı neden gerçekleştirdiğini görebilirsiniz:

  • foo işlev nesnesi başlatıldığında, foo.func_defs[0] değişmez bir nesne olan None olarak ayarlanır.
  • İşlev varsayılanlarla çalıştırıldığında (işlev çağrısında L için hiçbir parametre belirtilmemişse), yerel kapsamda foo.func_defs[0] (None) L olarak kullanılabilir.
  • L = [] üzerine, atama foo.func_defs[0]'da başarılı olamaz, çünkü bu özellik salt okunurdur.
  • (1) ,yerel kapsamda L olarak da adlandırılan yeni bir yerel değişkenve işlev çağrısının kalanı için kullanılır. foo.func_defs[0] böylece gelecekteki foo çağrıları için değişmeden kalır.
19
Dmitry Minkovsky

Buradaki çözümler:

  1. None öğesini varsayılan değeriniz olarak kullanın (veya nonce object) ve çalışma zamanında değerlerinizi oluşturmak için bunu açın; veya
  2. Varsayılan parametreniz olarak bir lambda kullanın ve bunu varsayılan değer elde etmek için bir try bloğu içinde çağırın (bu, lambda soyutlamasının içindir.

İkinci seçenek Güzel çünkü işlevin kullanıcıları önceden var olan bir çağrılabilir numaradan geçebilir (type gibi)

17
Marcin

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

Diğerlerinin kapsamlı bir şekilde yorum yaptığı gibi, list parametresi, yürütüldüğü zaman aksine tanımlandığı zaman işleve bağlanır. Listeler ve sözlükler değişken olduğundan, bu parametrede yapılacak herhangi bir değişiklik, bu fonksiyona yapılan diğer çağrıları etkileyecektir. Sonuç olarak, işleve yapılan sonraki çağrılar, işleve yapılan diğer çağrılar tarafından değiştirilmiş olabilecek bu paylaşılan listeyi alır. Daha da kötüsü, iki parametre bu işlevin paylaşılan parametresini aynı anda diğerinin yaptığı değişikliklere alışkın olarak kullanıyor.

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

id komutunu kullanarak tek ve aynı nesnenin olduğunu doğrulayabilirsiniz:

>>> id(a)
5347866528

>>> id(b)
5347866528

Brett Slatkin'in "Etkili Python: 59 Daha İyi Python Yazmanın Özel Yolları", Öğe 20: Dinamik varsayılan değişkenleri belirtmek için None ve Docstrings kullanın (s. 48)

Python'da istenen sonucu elde etmek için kullanılan kural, varsayılan None değerini sağlamak ve dokümanlardaki gerçek davranışı belgelemektir.

Bu uygulama, işleve yapılan her çağrı için ya varsayılan listeyi ya da işleve iletilen listeyi alır.

Tercih edilen yol :

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 parametresini paylaşılmasını istediği “Yanlış Yöntem” için meşru kullanım durumları olabilir, ancak bu kuraldan çok istisna olabilir.

17
Alexander

Bazen bu davranışı aşağıdaki kalıba alternatif olarak kullanırım:

singleton = None

def use_singleton():
    global singleton

    if singleton is None:
        singleton = _make_singleton()

    return singleton.use_me()

singleton yalnızca use_singleton tarafından kullanılıyorsa, aşağıdaki deseni değiştirmeyi seviyorum:

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

Bunu, dış kaynaklara erişen istemci sınıflarını başlatmak ve ayrıca notlandırma için alıntılar veya listeler oluşturmak için kullandım.

Bu örgünün iyi bilindiğini düşünmediğim için gelecekteki yanlış anlamaları önlemek için kısa bir yorum yapıyorum.

16
bgreen-litl

Bunu, nesneyi (ve dolayısıyla kapsamı kapsamıyla değiştirerek) değiştirerek halledebilirsiniz:

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

Çirkin, ama işe yarıyor.

15
jdborg

Bunu yaptığımızda:

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

... eğer arayan a'nın değerini geçmezse, a argümanını bir adsız listeye atarız.

Bu tartışma için işleri kolaylaştırmak için, geçici olarak adsız listeye bir ad verelim. Peki ya pavlo?

def foo(a=pavlo):
   ...

Herhangi bir zamanda, arayan bize a'nın ne olduğunu söylemezse, pavlo öğesini yeniden kullanırız.

pavlo değiştirilebilirse (değiştirilebilir) ve foo değiştirilirse, foo belirtilmeden a adlı bir dahaki sefere fark ettiğimiz bir etki.

Yani gördüğünüz budur (Unutmayın, pavlo [] için başlatıldı):

 >>> foo()
 [5]

Şimdi, pavlo [5].

foo() işlevini tekrar çağırmak, pavlo işlevini yeniden değiştirir:

>>> foo()
[5, 5]

foo() işlevini çağırırken a belirtilmesi pavlo öğesine dokunulmamasını sağlar.

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

Yani, pavlo hala [5, 5].

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

Doğru olabilir ki:

  1. Birileri her dil/kütüphane özelliğini kullanıyor ve
  2. Buradaki davranışı değiştirmek yanlış tavsiye edilir, ancak

yukarıdaki özelliklerin her ikisine birden sahip olmak ve yine başka bir noktaya değmek tamamen tutarlıdır:

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

Diğer cevaplar veya en azından bazıları ya 1 ve 2 puan alır, ancak 3 değil, 3 ve aşağı 3 ve 1 puan alır. Ama her üçü de geçerlidir.

Atların ortasındaki atları değiştirmenin önemli bir kırılma talep edeceği ve Python'un Stefano'nun açılış snippet'ini sezgisel olarak ele almasıyla değiştirilerek yaratılan daha fazla sorun olabileceği doğru olabilir. Ve Python'un içini iyi tanıyan birinin, bir mayın sonuçları alanını açıklayabileceği doğru olabilir. Ancak,

Var olan davranış Pythonic değildir ve Python başarılıdır, çünkü dil hakkında çok az şey, her yerde en az şaşkınlık ilkesini ihlal ediyor near this. Bu, onu sökmenin akıllıca olup olamayacağı gerçek bir sorundur. Bu bir tasarım hatasıdır. Davranışı izlemeye çalışarak dili çok daha iyi anlarsanız, C++ 'in 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'un bu davranış karşısında sebat edecek kadar önemseyen insanlar, diline çizilen insanlar çünkü Python'un diğer dillerden çok daha az sürprizleri var. Dabblers ve meraklıları, Pythonistas olurlar, bir şeyi elde etmek için ne kadar az zaman harcadıklarına şaşırırlar - bir tasarım fl yüzünden değil - Python'a çizilen programcıların sezgilerine aykırı olan gizli mantık bulmacası demek istiyorum. çünkü Just Works.

13
Christos Hayward

Bu "böcek" bana fazla mesai yaptı! Fakat bunun potansiyel bir kullanımını görmeye başlıyorum (ama yine de yürütme zamanında olmasını isterdim)

Size faydalı bir örnek olarak gördüğümü 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
9
Norfeldt

Bence bu sorunun cevabı python'un verilere parametreye nasıl geçtiğini (değere veya referansa göre), mutasyona değil de python'un "def" deyimini nasıl ele aldığına bağlı olduğunu düşünüyorum.

Kısa bir giriş. İlk olarak, python'da iki tür veri tipi vardır, biri basit temel veri türüdür, sayılar gibi, diğeri ise veri türüdür. İkincisi, verileri parametrelere geçirirken, python temel veri tipini değere göre iletir, yani değerin yerel bir kopyasını yerel bir değişkene dönüştürür, ancak referans olarak nesneyi işaret eden nesneye iletir.

Yukarıdaki iki noktayı itiraf ederek, python koduna ne olduğunu açıklayalım. Sadece nesneler için referans alarak, ancak değişken/değişmez olanla ya da tartışmasız "def" ifadesinin tanımlandığında sadece bir kez gerçekleştirilmiş olmasıyla ilgisi yoktur.

[] bir nesnedir, bu nedenle python [] öğesine a, yani a başvurusunu yalnızca nesne olarak bellekte yer alan [] işaretçisidir. Bununla birlikte, birçok referansı olan yalnızca [] kopyası vardır. İlk foo () için [] listesi append yöntemiyle 1 olarak değiştirildi. Ancak, liste nesnesinin yalnızca bir kopyasının bulunduğunu ve bu nesnenin şimdi 1 olduğunu unutmayın. İkinci foo'yu () çalıştırırken, effbot web sayfasının söylediği (öğeler artık değerlendirilmez) yanlış. a, liste nesnesi olarak değerlendirilir, ancak şimdi nesnenin içeriği 1 olur. Bu referansa geçmenin etkisidir! Foo (3) sonucu aynı şekilde kolayca elde edilebilir.

Cevabımı daha da doğrulamak için, iki ek koda bakalım.

====== No. 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, yani None (ikincisi değişmezken eski değişkendir. Ancak değişkenliğin soru ile ilgisi yoktur). Hiçbiri uzayda bir yerde değil ama orada olduğunu ve orada Yok'un tek bir kopyasının olduğunu biliyoruz. Bu nedenle, her bir foo çağrıldığında, öğeler (yalnızca bir kez değerlendirilen yanıtın aksine) Yok olarak, açık olması için Yok'un referansı (veya adresi) olarak değerlendirilir. Ardından foo'da öğe [] olarak değiştirilir, yani farklı bir adrese sahip başka bir nesneyi gösterir.

====== No. 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) çağrısı, öğeleri bir adresle [11111111] adresli bir liste nesnesine işaret eder. Listenin içeriği, sekanstaki foo işlevinde 1 olarak değiştirilir, ancak adres, değişti, hala 11111111. Sonra foo (2, []) geliyor. Foo (2, []) içindeki [], foo (1) çağrılırken [] varsayılan parametresiyle aynı içeriğe sahip olsa da, adresleri farklıdır! Parametreyi açıkça verdiğimiz için, items bu yeni [] adresini almalı, 2222222 diyelim ve bir değişiklik yaptıktan sonra geri döndürmelidir. Şimdi foo (3) yürütülür. Yalnızca x sağlandığından, öğelerin varsayılan değerini tekrar alması gerekir. Varsayılan değer nedir? Foo işlevi tanımlanırken ayarlanır: 11111111'de bulunan liste nesnesi. 11111111 adresinde yer alan liste nesnesi 11111111 adresi olarak değerlendirilir. 2222222 adresinde yer alan liste ayrıca bir öğe 2 içerir, ancak herhangi bir öğe tarafından Daha. Sonuç olarak, 3'ün eki items [1,3] yapacaktır.

Yukarıdaki açıklamalardan, kabul edilen cevapta önerilen effbot web sayfasının bu soruya alakalı bir cevap veremediğini görebiliriz. Dahası, effbot web sayfasındaki bir noktanın yanlış olduğunu düşünüyorum. Kullanıcı Arabirimi 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, i öğesinin farklı değerlerini gösterecek olan farklı bir geri çağırma işlevine sahip olabilir. Bunu göstermek için bir örnek verebilirim:

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

x[7]() işlevini çalıştırırsak, beklendiği gibi 7 elde ederiz ve x[9](), 9 değerini verir; i başka bir değer.

8
user2384994

Bu bir tasarım hatası değil . Bunu yapan herkes yanlış bir şey yapıyor.

Bu soruna nerede bulaştığınızı gördüğüm 3 vaka var:

  1. Argümanı fonksiyonun bir yan etkisi olarak değiştirmeyi düşünüyorsunuz. Bu durumda hiçbir zaman mantıklı değil varsayılan bir argüman olması. Bunun tek istisnası, işlev listesine sahip olmak için argüman listesini kötüye kullanmanızdır, örn. cache={}, fonksiyona gerçek bir argüman ile çağırmanız beklenmez.
  2. Argümanı değiştirilmemiş olarak bırakmayı düşünüyorsunuz, ancak yanlışlıkla did change. Bu bir hata, düzelt.
  3. İşlevin içinde kullanılacak argümanı değiştirmek niyetindesiniz, ancak değişimin işlevin dışında görüntülenmesini beklemediniz. Bu durumda, argümandan bir copy yapmanız gerekebilir, varsayılan olsun ya da olmasın! Python bir değere göre arama dili değil, bu nedenle kopyayı sizin için yapmıyor, bu konuda açık olmanız gerekiyor.

Sorudaki örnek kategori 1 veya 3'e girebilir. Hem geçen listeyi değiştirmesi hem de döndürmesi gariptir; birini ya da diğerini seçmelisin.

8
Mark Ransom

Sadece işlevi değiştirin:

def notastonishinganymore(a = []): 
    '''The name is just a joke :)'''
    a = a[:]
    a.append(5)
    return a
7
ytpillai

TLDR: Zaman tanımlarını tanımla tutarlı ve kesinlikle daha etkileyici.


Bir işlev tanımlamak iki kapsamı etkiler: işlevi tanımlayan kapsamı [ fonksiyonu ve uygulama kapsamı fonksiyonun içermesi. Haritanın kapsamlara nasıl gireceği oldukça açık olsa da, soru, def <name>(<args=defaults>): öğesinin ait olduğu yerdir:

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

def name bölümü must, tanımlayıcı kapsamda değerlendirilir - sonuçta name dosyasının orada olmasını istiyoruz. Fonksiyonu sadece kendi içinde değerlendirmek erişilmez hale getirir.

parameter sabit bir ad olduğundan, def name ile aynı anda "değerlendirebiliriz". Bu ayrıca, çıplak name(parameter=...): yerine name(...): olarak bilinen bir imzayla işlevi üretme avantajına sahiptir.

Şimdi, ne zaman default değerlendirmek için?

Tutarlılık zaten "tanımda" diyor: def <name>(<args=defaults>): dışındaki her şey en iyi tanımda da değerlendiriliyor. Bunun parçalarını geciktirmek şaşırtıcı bir seçim olacaktır.

İki seçenek de aynı değildir: Eğer tanımlanabilir zamanda default değerlendirilirse, hala yürütme süresini etkileyebilir. default yürütme sırasında değerlendirilirse, tanım zamanını olamaz etkiler. "Tanımda" seçimi, her iki vakayı da ifade etmeyi sağlarken, "uygulamada" seçimi ise yalnızca birini ifade edebilir:

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

def name(parameter=None):     # delay default until execution time
    parameter = [] if parameter is None else parameter
    ...
4
MisterMiyagi

Diğer her cevap bunun neden Güzel ve istenen bir davranış olduğunu ya da neden buna hiç ihtiyaç duymamanız gerektiğini açıklar. Benimki dilin bükülme hakkını, istediği şekilde değil, istediği gibi kullanmak isteyenler için.

Bu davranışı, varsayılan değerinde bırakılan her bir konumsal argüman için aynı örneği tekrar 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 fonksiyonumuzu yeniden tanımlayalım:

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

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

Bu, özellikle birden fazla argüman alan işlevler için temizdir. 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 sözcükler kullanmaya çalışırsanız, yukarıdaki çözümün bozulduğunu unutmayın:

foo(a=[4])

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

0
Przemek D