2012-09-14 34 views
86

Tamam, bu konuda benimle konuş, korkunç bir şekilde kıvrılmaya başlayacağını biliyorum, ama lütfen bana neler olduğunu anlatayım.Python iç içe geçmiş işlevlerindeki yerel değişkenler

from functools import partial 

class Cage(object): 
    def __init__(self, animal): 
     self.animal = animal 

def gotimes(do_the_petting): 
    do_the_petting() 

def get_petters(): 
    for animal in ['cow', 'dog', 'cat']: 
     cage = Cage(animal) 

     def pet_function(): 
      print "Mary pets the " + cage.animal + "." 

     yield (animal, partial(gotimes, pet_function)) 

funs = list(get_petters()) 

for name, f in funs: 
    print name + ":", 
    f() 

verir:

cow: Mary pets the cat. 
dog: Mary pets the cat. 
cat: Mary pets the cat. 

Yani temelde, neden üç farklı hayvanları alamıyorum? cage iç içe geçmiş işlevin yerel kapsamına 'dahil değil mi? Değilse, iç içe geçmiş işleve yapılan çağrı yerel değişkenlere nasıl bakar?

Bu tür problemlerle karşılaşmanın genellikle birinin 'yanlış yaptığı' anlamına geldiğini biliyorum, ancak ne olduğunu anlamak istiyorum.

+1

deneyin'. .. Eminim birisi bir araya gelip bunu açıklayacaktır - bu Python gotcha'nın :) –

cevap

93

Yuvalanmış işlev, tanımlandığında değil, yürütüldüğünde üst kapsamdaki değişkenleri arar.

İşlev gövdesi derlenir ve 'serbest' değişkenler (atanarak işlevde tanımlanmamıştır) doğrulanır, ardından işleve kapama hücreleri olarak bağlanır ve her bir hücrenin başvurusu için bir dizin kullanılarak kodlanır. pet_function, bir serbest değişkene (cage) sahiptir, bu daha sonra bir kapanma hücresi, dizin 0 yoluyla referanslandırılır. Kapatma, get_petters işlevinde yerel değişken cage işaret eder.

aslında işlevini çağırmak

, kapatma sonra işlevini diyoruz anda çevredeki kapsam içinde cage değerine bakmak için kullanılır. İşte problem yatıyor. İşlevlerinizi çağırdığınız zaman, get_petters işlevi zaten sonuçlandırılmıştır. Yürütme sırasında bir noktada cage yerel değişkeni, 'cow', 'dog' ve 'cat' dizgilerinin her birine atanmıştır, ancak işlevin sonunda cage bu son değeri 'cat' içerir. Böylece, dinamik olarak döndürülen işlevlerin her birini çağırdığınızda, 'cat' değeri yazdırılır.

Çalışma etrafı, kapatmaya dayanmamaktır. Bir kısmi işlevini yerine yeni işlev kapsamı oluşturabilir veya değişkeni anahtar sözcük parametresi için varsayılan değeri olarak kullanabilirsiniz. functools.partial() kullanılarak

  • Kısmi işlevi örneği:

    from functools import partial 
    
    def pet_function(cage=None): 
        print "Mary pets the " + cage.animal + "." 
    
    yield (animal, partial(gotimes, partial(pet_function, cage=cage))) 
    
  • , yeni bir kapsam örnek oluşturma: bir anahtar kelime parametresinin varsayılan değer olarak değişken Bağlanma

    def scoped_cage(cage=None): 
        def pet_function(): 
         print "Mary pets the " + cage.animal + "." 
        return pet_function 
    
    yield (animal, partial(gotimes, scoped_cage(cage))) 
    
  • :

    def pet_function(cage=cage): 
        print "Mary pets the " + cage.animal + "." 
    
    yield (animal, partial(gotimes, pet_function)) 
    

Döngüde scoped_cage işlevini tanımlamanıza gerek yoktur, derleme, yalnızca döngüyü her yinelemede değil, bir kez gerçekleşir.

+0

biri Bu duvarda kafamı iş için bir senaryo üzerinde bugün 3 saat boyunca çarptım. Son noktanız çok önemlidir ve bu problemle karşılaşmamın temel sebebidir. Kodum boyunca bolca kapatılmış geri aramalarım var, ama aynı tekniği bir döngüde denemek bana sahip olan şey. – DrEsperanto

5

Bu lazily son değer olarak depolanır i değerini yineleme sonra aşağıdaki

for i in range(2): 
    pass 

print i is 1 

kaynaklanmaktadır.

işlevi çalışacak bir jeneratör olarak

(yani sırayla her değere baskı), ancak o jeneratör üzerinde çalışan bir listeye dönüştürerek, cage (cage.animal) dolayısıyla tüm çağrılar kedileri döner.

11

Anlayışım, pet_function verilmişken, daha önce değil, ana işlev ad alanında kafesin aranmasıdır.

Yani sen son olarak oluşturulan kafes bulacaksınız 3 fonksiyonları oluşturmak

funs = list(get_petters()) 

bir söz vardır. Eğer biriyle en son döngü değiştirin

:

for name, f in get_petters(): 
    print name + ":", 
    f() 

Aslında alacak: [ 'kedi', 'köpek', 'inek'] `de hayvan için

cow: Mary pets the cow. 
dog: Mary pets the dog. 
cat: Mary pets the cat. 
İlgili konular