Python değiştirilebilir varsayılan argümanı: Neden?


20

Varsayılan argümanların fonksiyon başlatma zamanında yaratıldığını biliyorum, fonksiyon her çağrıldığında değil. Aşağıdaki koda bakın:

def ook (item, lst=[]):
    lst.append(item)
    print 'ook', lst

def eek (item, lst=None):
    if lst is None: lst = []
    lst.append(item)
    print 'eek', lst

max = 3
for x in xrange(max):
    ook(x)

for x in xrange(max):
    eek(x)

Elimde olmayan bunun neden bu şekilde uygulandığını. Bu davranış, her çağrı zamanında başlatma sırasında ne gibi avantajlar sağlar?


Bu, Stack Overflow'daki şaşırtıcı ayrıntıda zaten tartışıldı : stackoverflow.com/q/1132941/5419599
Wildcard

Yanıtlar:


14

Bunun sebebi uygulama basitliği. Açıklayayım.

Fonksiyonun varsayılan değeri, değerlendirmeniz gereken bir ifadedir. Sizin durumunuzda, kapatmaya bağlı olmayan basit bir ifade, ancak serbest değişkenler içeren bir şey olabilir - def ook(item, lst = something.defaultList()). Python tasarlayacaksanız, bir seçeneğiniz olacaktır - işlev tanımlandığında veya işlev her çağrıldığında bunu bir kez değerlendirir misiniz? Python ilkini seçer (ikinci seçenekle giden Ruby'nin aksine).

Bunun bazı faydaları var.

İlk olarak, biraz hız ve hafıza artışı elde edersiniz. Çoğu durumda değiştirilemez varsayılan argümanlarınız olur ve Python bunları her işlev çağrısı yerine yalnızca bir kez oluşturabilir. Bu (bazı) bellek ve zaman tasarrufu sağlar. Tabii ki, değişebilir değerlerle pek iyi çalışmıyor, ancak nasıl dolaşabileceğinizi biliyorsunuz.

Diğer bir fayda basitliktir. İfadenin nasıl değerlendirildiğini anlamak oldukça kolaydır - işlev tanımlandığında sözcüksel kapsamı kullanır. Diğer yöne giderse, sözcüksel kapsam tanım ve çağırma arasında değişebilir ve hata ayıklamayı biraz zorlaştırabilir. Python, bu durumlarda son derece basit olmak için uzun bir yol kat ediyor.


3
İlginç nokta - Python ile genellikle en az sürpriz ilkesi olsa da. Bazı şeyler model-biçimsel bir karmaşıklık açısından basit, ancak açık ve şaşırtıcı değildir ve bence bu önemlidir.
Steve314

1
Burada en az sürpriz olan şey şudur: her çağrıda değerlendirme semantiğine sahipseniz, iki işlev çağrısı arasında kapanma değişirse (bu oldukça mümkündür) bir hata alabilirsiniz. Bu, bir kez değerlendirildiğini bilmek yerine daha şaşırtıcı olabilir. Tabii ki, diğer dillerden geldiğinizde, her çağrıda semantiği değerlendirmek dışında bir şey olduğunu ve bunun sürpriz olduğunu iddia edebilirsiniz, ancak her iki yönde de nasıl gittiğini görebilirsiniz :)
Stefan Kanev

Kapsam hakkında iyi bir nokta
0xc0de

Bence kapsam aslında daha önemli. Varsayılan için sabitlerle sınırlı olmadığınızdan, çağrı sitesinde kapsamı olmayan değişkenlere gereksinim duyabilirsiniz.
Mark Ransom

5

Koymak için bir yolu olduğunu lst.append(item) gelmez mutasyona lstparametresi. lsthala aynı listeye gönderme yapıyor. Sadece bu listenin içeriği değiştirildi.

Temel olarak, Python hiç sabit veya değişmez değişkene sahip değildir (hatırlıyorum) - ama bazı sabit, değişmez tiplere sahip. Bir tamsayı değerini değiştiremezsiniz, yalnızca değiştirebilirsiniz. Ancak bir listenin içeriğini değiştirmeden değiştirebilirsiniz.

Bir tamsayı gibi, bir referansı değiştiremezsiniz, yalnızca onu değiştirebilirsiniz. Ancak, başvurulan nesnenin içeriğini değiştirebilirsiniz.

Varsayılan nesneyi bir kez oluşturmaya gelince, bunun nesne oluşturma ve çöp toplama ek yüklerinden tasarruf etmek için çoğunlukla bir optimizasyon olduğunu hayal ediyorum.


+1 Kesinlikle. Dolaylı katmanı anlamak önemlidir - bir değişkenin bir değer olmadığını ; bunun yerine başvuran bir değer. Bir değişkeni değiştirmek için değer değiştirilebilir veya değiştirilebilir (değiştirilebilirse).
Joonas Pulakka

Python değişkenleri içeren zor bir şeyle karşı karşıya kaldığımda, "=" işlevini "ad bağlama operatörü" olarak değerlendirmeyi yararlı buluyorum; bağladığımız şey yeni (yeni nesne veya değişmez tip örneği) olsun ya da olmasın, ad her zaman geri döner.
StarWeaver

4

Bu davranış, her çağrı zamanında başlatma sırasında ne gibi avantajlar sağlar?

Örneğinizde gösterildiği gibi istediğiniz davranışı seçmenizi sağlar. Varsayılan bağımsız değişkenin değiştirilemez olmasını istiyorsanız , veya gibi değiştirilemez bir değer kullanırsınız . Varsayılan bağımsız değişkeni değiştirilebilir yapmak istiyorsanız, gibi değiştirilebilir bir şey kullanırsınız . Kuşkusuz da olsa, sadece esneklik, bilmiyorsanız ısırır.None1[]


2

Bence asıl cevap şudur: Python prosedürel bir dil olarak yazılmıştır ve sadece olaydan sonra işlevsel yönleri benimsemiştir. Aradığın parametre parametresinin bir kapatma olarak yapılması ve Python'daki kapanışların gerçekten sadece yarı pişmiş olması. Bu deneyin kanıtı için:

a = []
for i in range(3):
    a.append(lambda: i)
print [ f() for f in a ]

bu da [2, 2, 2]gerçek bir kapanma olmasını beklediğiniz yere[0, 1, 2] .

Python'un parametre varsayılan değerlerini kapatmalarda sarmalama yeteneğine sahip olsaydım çok şey var. Örneğin:

def foo(a, b=a.b):
    ...

Son derece kullanışlı olurdu, ancak "a" işlev tanımı zamanında kapsamda değildir, bu yüzden bunu yapamazsınız ve bunun yerine tıknaz yapmak zorunda kalırsınız:

def foo(a, b=None):
    if b is None:
        b = a.b

Bu neredeyse aynı şey ... neredeyse.



1

Çok büyük bir fayda, notlaşmadır. Bu standart bir örnektir:

def fibmem(a, cache={0:1,1:1}):
    if a in cache: return cache[a]
    res = fib(a-1, cache) + fib(a-2, cache)
    cache[a] = res
    return res

ve karşılaştırma için:

def fib(a):
    if a == 0 or a == 1: return 1
    return fib(a-1) + fib(a-2)

İpython'da zaman ölçümleri:

In [43]: %time print(fibmem(33))
5702887
CPU times: user 0 ns, sys: 0 ns, total: 0 ns
Wall time: 200 µs

In [43]: %time print(fib(33))
5702887
CPU times: user 1.44 s, sys: 15.6 ms, total: 1.45 s
Wall time: 1.43 s

0

Bunun nedeni, Python'daki derlemenin açıklayıcı kod çalıştırılarak gerçekleştirilmesidir.

Biri derseniz

def f(x = {}):
    ....

her seferinde yeni bir dizi istediğiniz oldukça açık olacaktır.

Ama ya dersem:

list_of_all = {}
def create(stuff, x = list_of_all):
    ...

Burada, çeşitli listelere bir şeyler oluşturmak ve bir liste belirtmediğimde tek bir global catch-all'a sahip olmak istiyorum sanırım.

Ancak derleyici bunu nasıl tahmin ederdi? Peki neden denesiniz? Bunun adlandırılıp adlandırılmadığına güvenebiliriz ve bazen yardımcı olabilir, ama gerçekten sadece tahmin ederdi. Aynı zamanda, denememek için iyi bir neden var - tutarlılık.

Olduğu gibi, Python sadece kodu yürütür. List_of_all değişkenine zaten bir nesne atanır, böylece bu nesne, herhangi bir işleve yapılan çağrı burada adlandırılan yerel bir nesneye başvuru alacağı şekilde x değerini varsayılan olan koda referans olarak geçirilir.

Adlandırılmamış adı belirtilen durumdan ayırmak istersek, derleme yürütme kodunu çalışma zamanında yürütüldüğünden önemli ölçüde farklı bir şekilde içerirdi. Bu yüzden özel davayı yapmıyoruz.


-5

Bunun nedeni, Python'daki işlevlerin birinci sınıf nesneler olmasıdır :

Varsayılan parametre değerleri, işlev tanımı yürütüldüğünde değerlendirilir. Bu, ifadenin işlev tanımlandığında bir kez değerlendirildiği ve her çağrı için aynı “önceden hesaplanmış” değerin kullanıldığı anlamına gelir .

Parametre değerini düzenlemenin sonraki çağrılar için varsayılan değeri değiştirdiğini ve işlev gövdesinde açık bir testle Yok'u varsayılan olarak kullanmanın basit bir çözümünün, hiçbir sürpriz sağlamak için gerekli olan tek şey olduğunu açıklar.

Yani def foo(l=[]) , çağrıldığında bu işlevin bir örneği olur ve sonraki çağrılar için yeniden kullanılır. İşlev parametrelerini bir nesnenin niteliklerinden ayrı olarak düşünün.

Profesyoneller, sınıfların C benzeri statik değişkenlere sahip olmasını sağlamak için bunu kullanabilir. Bu nedenle, varsayılan değerleri Yok olarak bildirmek ve bunları gerektiği gibi başlatmak en iyisidir:

class Foo(object):
    def bar(self, l=None):
        if not l:
            l = []
        l.append(5)
        return l

f = Foo()
print(f.bar())
print(f.bar())

g = Foo()
print(g.bar())
print(g.bar())

verim:

[5] [5] [5] [5]

beklenmedik yerine:

[5] [5, 5] [5, 5, 5] [5, 5, 5, 5]


5
Hayır. Her bir çağrı için varsayılan bağımsız değişken ifadesini tekrar değerlendirmek üzere işlevleri farklı şekilde tanımlayabilirsiniz (birinci sınıf ya da değil). Ve bundan sonraki her şey, yani cevabın kabaca% 90'ı tamamen sorunun yanında. -1

1
Öyleyse, her çağrı için varsayılan arg değerini değerlendirmek için işlevleri nasıl tanımlayacağımızla ilgili bu bilgiyi bizimle paylaşın, Python Docs'un önerdiğinden daha basit bir yol bilmek istiyorum .
ters çevir

2
Bir dil tasarımı düzeyinde demek istiyorum. Python dil tanımı şu anda varsayılan bağımsız değişkenlere oldukları gibi davranıldığını belirtmektedir; varsayılan argümanların başka bir şekilde ele alındığını eşit derecede iyi ifade edebilir. IOW cevap veriyorsun "işler böyle" sorusuna "neden işler oldukları gibi".

Python, Coffeescript'in yaptığı gibi varsayılan parametreler uygulamış olabilir. Eksik parametreleri kontrol etmek için bayt kodu ekler ve eğer eksiklerse ifadeyi değerlendirir.
Winston Ewert
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.