2012-08-06 39 views
6

Aşağıdaki işlev, önceden hesaplanmış değerlerin sonuçlarını saklayan bir dekoratör olarak kullanılmak içindir. cache işlev nesnesinin bir özelliği olması gerekmez ki (yanlışlıkla)Birden fazla dekore edilmiş işlev çağrısında veriler nasıl kalıcı kalabilir?

def cached(f): 
    f.cache = {} 
    def _cachedf(*args): 
     if args not in f.cache: 
      f.cache[args] = f(*args) 

     return f.cache[args] 

    return _cachedf 

anladım: argüman zaten daha önce hesaplanmıştır, işlev cache sözlükte saklanan değer dönecektir. gerçeklerin ki, şu kod yanı çalışır:

def cached(f): 
    cache = {} # <---- not an attribute this time! 
    def _cachedf(*args): 
     if args not in cache: 
      cache[args] = f(*args) 

     return cache[args] 
    return _cachedf 

Ben cache nesne birden çağrıları arasında kalıcı nasıl olabilir anlamakta zorlanıyorum. Birden çok önbelleğe alınmış işlevi çağırmayı denedim ve herhangi bir çakışma veya sorun bulamadım.

_cachedf işlevi döndürüldükten sonra bile cache değişkeninin hala nasıl olduğunu anlamanıza yardımcı olabilir misiniz?

cevap

11

closure burada oluşturuyorsunuz: _cachedf() işlevi, cache değişkeninin kapsamını kapsayıcı kapsamdan kapatır. Bu, işlev nesnesi yandığı sürece cache'u canlı tutar.

Edit: Belki de Python'da bunun nasıl çalıştığına ve CPython'un bunu nasıl uyguladığına dair birkaç ayrıntı eklemeliyim. Daha basit bir örnek üzerinde

inceleyelim: fonksiyonunu f() içeren modülün derleme sırasında interaktif tercüman

>>> h = f() 
>>> h() 
1 
>>> h() 
2 
>>> h() 
3 

yılında

def f(): 
    a = [] 
    def g(): 
     a.append(1) 
     return len(a) 
    return g 

Örnek kullanım, derleyici fonksiyonu olduğunu g() başvuruları görür kapsamındaki a adının kapsamı ve bu dış başvuruyu kodundaki kodda hafızaya alır. f() işlevine yanıt (özellikle, a adını f.__code__.co_cellvars'a ekler).

f() işlevi çağrıldığında ne olur? İlk satır yeni bir liste nesnesi oluşturur ve onu a adına bağlar. Sonraki satır , yeni bir işlev nesnesi ( sırasında modülün derlenmesi sırasında oluşturulan kod nesnesini kullanarak) oluşturur ve g adına bağlar. g() numaralı gövde bu noktada yürütülmez ve son olarak, funciton nesnesi döndürülür. f() girildiğinde

f() kod nesne adı a yerel fonksiyonları tarafından başvurulan olduğunu not sahip olduğundan

, bu ad için bir "hücre" oluşturulur. Bu hücre, asıl listeye ilişkin nesnesinin referansını içerir. a nesneye bağlanır ve g() işlevi, bu hücre için öğesine bir başvuru alır. Bu şekilde, liste nesnesinin ve hücrenin, f() funciton çıktığı zaman bile canlı tutulur.

+0

Açıklama için çok teşekkürler, düzenlemeniz bazı şeyleri netleştirir. Merak ettim, (C) Python'un içsellerini öğrenmek için, son paragrafınızda bahsettiğiniz "hücre" ye, muayene ya da benzer bir şeyden söz etmek mümkün mü? – rahmu

+0

@rahmu: Açıklamada bir hatayı düzelttim (çok fazla değişmez). Ne yazık ki, hücreler Python kodu için tamamen saydamdır ve her zaman başvurdukları nesne ile değiştirilir, böylece incelenemezler. –

3

Herkes, _cachedf işlevi döndükten sonra bile önbellek değişkeninin hala nasıl olduğunu anlamanıza yardımcı olabilir mi?

Bu, Python'un referans sayma çöp toplayıcısına aittir. cache değişkeni _cachedf işlevinin bir referansı olduğundan ve cached numaralı arayanın buna bir başvurusu olduğundan korunacak ve erişilebilir olacaktır. Fonksiyonu tekrar çağırdığınızda, orijinal olarak oluşturulmuş aynı işlev nesnesini kullanıyorsunuz, dolayısıyla önbelleğe erişebilirsiniz.

Tüm referanslar yok olana kadar önbelleği kaybetmezsiniz. Bunu yapmak için del operatörünü kullanabilirsiniz.

Kayıt için
>>> import time 
>>> def cached(f): 
...  cache = {} # <---- not an attribute this time! 
...  def _cachedf(*args): 
...   if args not in cache: 
...    cache[args] = f(*args) 
...   return cache[args] 
...  return _cachedf 
...  
... 
>>> def foo(duration): 
...  time.sleep(duration) 
...  return True 
...  
... 
>>> bob = cached(foo) 
>>> bob(2) # Takes two seconds 
True 
>>> bob(2) # returns instantly 
True 
>>> del bob # Deletes reference to bob (aka _cachedf) which holds ref to cache 
>>> bob = cached(foo) 
>>> bob(2) # takes two seconds 
True 
>>> 

, ne ulaşmaya çalıştığınız Memoization denir ve aynı şeyi yapar decorator pattern page edinilebilir daha eksiksiz memoizing dekoratör vardır, ancak kullanıyor: Örneğin

dekoratör sınıfı. Kodunuz ve sınıf tabanlı dekoratör, temelde aynıdır; sınıf tabanlı dekoratör, depolamadan önce karma yeteneğini kontrol eder.


Düzenleme (2017/02/02): @SiminJie cached(foo)(2) daima bir gecikme katlandığı yorumlar.

Bunun nedeni, cached(foo)'un yeni bir işlevi yeni bir önbellek ile döndürmesidir. cached(foo)(2) çağrıldığında, yeni bir (boş) önbellek oluşturulur ve önbelleğe alınmış işlev hemen çağrılır.

Önbellek boş olduğundan ve değeri bulamadığı için, temel işlevi yeniden çalıştırır. Bunun yerine, cached_foo = cached(foo) yapın ve daha sonra birden çok kez cached_foo(2) numaralı telefonu arayın. Bu sadece ilk görüşme için gecikmeye neden olacaktır. Eğer dekoratörler aşina değilseniz

@cached 
def my_long_function(arg1, arg2): 
    return long_operation(arg1,arg2) 

my_long_function(1,2) # incurs delay 
my_long_function(1,2) # doesn't 

Yukarıdaki kod ne anlama geldiğini anlamak için this answer bakmak: Bir dekoratör olarak kullanılan eğer Ayrıca, beklendiği gibi çalışır.

+0

Bu python dekoratör için nasıl çalışır? Her zaman önbelleğe al (foo) (2) 'dediğimde, sonucu önbelleğe almaz ve iki saniye uyur. Her dekore edilmiş işlev çağrısı aynı dekoratöre başvurur mu? –

+0

@SiminJie - Cevabı ek düzenlememe bakın. – brice

İlgili konular