2011-03-29 54 views
15

我是Python裝飾器(哇,偉大的功能!)的新手,我無法獲得以下工作,因爲self參數混合起來。Python裝飾器,自我混合

#this is the decorator 
class cacher(object): 

    def __init__(self, f): 
     self.f = f 
     self.cache = {} 

    def __call__(self, *args): 
     fname = self.f.__name__ 
     if (fname not in self.cache): 
      self.cache[fname] = self.f(self,*args) 
     else: 
      print "using cache" 
     return self.cache[fname] 

class Session(p.Session): 

    def __init__(self, user, passw): 
     self.pl = p.Session(user, passw) 

    @cacher 
    def get_something(self): 
     print "get_something called with self = %s "% self 
     return self.pl.get_something() 

s = Session(u,p) 
s.get_something() 

當我跑,我得到:

get_something called with self = <__main__.cacher object at 0x020870F0> 
Traceback: 
... 
AttributeError: 'cacher' object has no attribute 'pl' 

的,我做self.cache[fname] = self.f(self,*args)

問題行 - 很顯然,問題是self是cacher的對象,而不是的Session實例,它確實沒有pl屬性。但是我找不到如何解決這個問題。

解決方案,我認爲,但不能使用 - 我想使裝飾類(本article的2.1節等)返回一個函數,而不是一個值,從而self是在正確的上下文中計算的,但這是不可能的,因爲我的裝飾器是作爲一個類實現的,並使用內置的__call__方法。然後我想到而不是爲我的裝飾器使用了一個類,這樣我就不需要__call__方法了,但是我不能這樣做,因爲我需要在裝飾器調用之間保持狀態(即用於保持跟蹤self.cache屬性)。

問題 - 因此,除了使用全局cache字典變量(我沒有嘗試,但假設將工作),還有沒有其他的方法,使這項工作裝飾?

編輯:這太問題似乎類似Decorating python class methods, how do I pass the instance to the decorator?

+0

您知道這樣,'Session'的所有實例將共享相同的緩存? – 2011-03-29 08:49:56

+0

是的,我還沒有完成代碼,但我粗略地想到給一個額外的會話參數來保存單獨的緩存。 – Rabarberski 2011-03-29 08:55:44

回答

27

使用descriptor protocol這樣的:

import functools 

class cacher(object): 

    def __init__(self, f): 
     self.f = f 
     self.cache = {} 

    def __call__(self, *args): 
     fname = self.f.__name__ 
     if (fname not in self.cache): 
      self.cache[fname] = self.f(self,*args) 
     else: 
      print "using cache" 
     return self.cache[fname] 

    def __get__(self, instance, instancetype): 
     """Implement the descriptor protocol to make decorating instance 
     method possible. 

     """ 

     # Return a partial function with the first argument is the instance 
     # of the class decorated. 
     return functools.partial(self.__call__, instance) 

編輯:

它是如何工作的?

使用在裝飾描述符的協議將允許我們訪問裝飾用正確的實例作爲自我的方法,也許一些代碼可以幫助更好地:

現在,當我們將做到:

class Session(p.Session): 
    ... 

    @cacher 
    def get_something(self): 
     print "get_something called with self = %s "% self 
     return self.pl.get_something() 

相當於:

class Session(p.Session): 
    ... 

    def get_something(self): 
     print "get_something called with self = %s "% self 
     return self.pl.get_something() 

    get_something = cacher(get_something) 

所以現在get_something是cacher的一個實例。因此,當我們將調用方法get_something它會被轉換到這個(因爲描述符協議):

session = Session() 
session.get_something 
# <==> 
session.get_something.__get__(get_something, session, <type ..>) 
# N.B: get_something is an instance of cacher class. 

並且因爲:

session.get_something.__get__(get_something, session, <type ..>) 
# return 
get_something.__call__(session, ...) # the partial function. 

所以

session.get_something(*args) 
# <==> 
get_something.__call__(session, *args) 

希望這將說明它是如何工作的:)

+0

這的確是這樣做的,儘管我仍然不得不在自己提供的鏈接中圍繞描述來理解到底發生了什麼。我猜''self'(in'f(self,* args)')通過自動調用'__get__'來解析。但是,在所有其他情況下,這不會爲「自我」提供錯誤的解決方案,例如'self.cache [fname]',甚至'self.f'? – Rabarberski 2011-03-29 09:20:10

+0

@Rabarberski:我剛剛編輯了我的答案,包括一個解釋,希望它會很有用:) – mouad 2011-03-29 09:37:41

1

首先,你明確地通過cacher ob在下面的行JECT作爲第一個參數:

self.cache[fname] = self.f(self,*args) 

的Python會自動添加到self的只有方法的參數列表。它將類名稱空間中定義的函數(但不包括其他可調用對象,如cacher對象!)轉換爲方法。爲了得到這樣的行爲,我看到兩種方法:

  1. 通過使用閉包將您的修飾器更改爲返回函數。
  2. 執行描述符協議以便自己傳遞self參數,因爲它在memoize decorator recipe中完成。
+0

我認爲解決方案(1)不適用於我在帖子中給出的參數。或者我錯了嗎?解決方案(2)似乎與奇點的答案相同。感謝您的memoize鏈接! – Rabarberski 2011-03-29 09:27:32

+0

我看到的唯一參數是保持呼叫之間的狀態。當然,可以通過可變參數[s]來裝飾器函數。 – 2011-03-29 09:39:25

3

由於您不需要使用描述符協議,所以關閉通常是更好的方法。通過調用保存可變狀態比使用類更容易,因爲您只需將可變對象粘貼在包含範圍內(對不可變對象的引用可以通過關鍵字nonlocal進行處理,也可以將它們存儲在可變對象中,條目列表)。

#this is the decorator 
from functools import wraps 
def cacher(f): 
    # No point using a dict, since we only ever cache one value 
    # If you meant to create cache entries for different arguments 
    # check the memoise decorator linked in other answers 
    print("cacher called") 
    cache = [] 
    @wraps(f) 
    def wrapped(*args, **kwds): 
     print ("wrapped called") 
     if not cache: 
      print("calculating and caching result") 
      cache.append(f(*args, **kwds)) 
     return cache[0] 
    return wrapped 

class C: 
    @cacher 
    def get_something(self): 
     print "get_something called with self = %s "% self 

C().get_something() 
C().get_something() 

如果你不完全熟悉的方式關閉工作,增加更多的打印語句(如我上面)即可說明。您將看到cacher僅在函數被定義時才被調用,但每次調用該方法時都會調用wrapped

這確實突出顯示瞭如何使用記憶技術和實例方法時要小心 - 如果您不小心考慮self的值的變化,您將最終在實例之間共享緩存的答案,這可能不會成爲你想成爲的人。

+0

嗯,我不明白。在調用之間不會保留緩存變量內容,不是?我查了一下'wraps',但是我認爲我受到信息超載的困擾,'partial()'(來自之前的答案)的含義和用處正慢慢地流過。最後,這個詞典是必要的,因爲你需要爲你應用'cacher'的每個函數使用不同的緩存(鍵),否? – Rabarberski 2011-03-29 13:25:56

+0

不,因爲緩存是在調用「cacher」時創建的,只發生在函數定義時。 'wrapped'是同時創建的,並且每個'wrapped'的調用都會看到相同的原始'cache'定義。 'wraps()'只是一個幫助器,將'__name__','__doc__'和其他細節從'f'複製到'wrapped',所以裝飾函數看起來更像原文。 – ncoghlan 2011-03-30 03:52:03

+0

這個關於裝飾器如何工作的解釋也可能有幫助:http://stackoverflow.com/questions/5481739/how-does-this-python-decorator-work – ncoghlan 2011-03-30 05:07:51