2011-09-20 131 views
11

我試圖將可選參數傳遞給我的類裝飾器在python中。 下面的代碼我目前有:Python類裝飾器參數

class Cache(object): 
    def __init__(self, function, max_hits=10, timeout=5): 
     self.function = function 
     self.max_hits = max_hits 
     self.timeout = timeout 
     self.cache = {} 

    def __call__(self, *args): 
     # Here the code returning the correct thing. 


@Cache 
def double(x): 
    return x * 2 

@Cache(max_hits=100, timeout=50) 
def double(x): 
    return x * 2 

第二個裝飾用的參數覆蓋(在我__init__功能max_hits=10, timeout=5)默認的,不工作,我得到異常TypeError: __init__() takes at least 2 arguments (3 given)。我嘗試了很多解決方案,並閱讀了關於它的文章,但在這裏我仍然無法完成工作。

任何想法解決這個?謝謝!

回答

12

@Cache(max_hits=100, timeout=50)來電__init__(max_hits=100, timeout=50),所以你不滿足function的說法。

您可以通過檢測函數是否存在的包裝器方法來實現您的裝飾器。如果它找到一個函數,它可以返回Cache對象。否則,它可以返回一個將用作裝飾器的包裝函數。

class _Cache(object): 
    def __init__(self, function, max_hits=10, timeout=5): 
     self.function = function 
     self.max_hits = max_hits 
     self.timeout = timeout 
     self.cache = {} 

    def __call__(self, *args): 
     # Here the code returning the correct thing. 

# wrap _Cache to allow for deferred calling 
def Cache(function=None, max_hits=10, timeout=5): 
    if function: 
     return _Cache(function) 
    else: 
     def wrapper(function): 
      return _Cache(function, max_hits, timeout) 

     return wrapper 

@Cache 
def double(x): 
    return x * 2 

@Cache(max_hits=100, timeout=50) 
def double(x): 
    return x * 2 
+0

感謝你們和@lunixbochs的解決方案!像一個魅力工作:) – Dachmt

+3

如果開發人員用位置而不是關鍵字參數調用'Cache'(例如'@Cache(100,50)'),那麼'function'將被分配值100和'max_hits' 50.一個在調用該函數之前不會引發錯誤。這可以被認爲是令人驚訝的行爲,因爲大多數人期望統一的位置和關鍵字語義。 – unutbu

11
@Cache 
def double(...): 
    ... 

相當於

def double(...): 
    ... 
double=Cache(double) 

雖然

@Cache(max_hits=100, timeout=50) 
def double(...): 
    ... 

相當於

def double(...): 
    ... 
double = Cache(max_hits=100, timeout=50)(double) 

Cache(max_hits=100, timeout=50)(double)Cache(double)具有非常不同的語義。

試圖讓Cache處理這兩種用例是不明智的。

你也可以使用一個裝飾的工廠,可以採取可選max_hitstimeout參數,並返回一個裝飾:

class Cache(object): 
    def __init__(self, function, max_hits=10, timeout=5): 
     self.function = function 
     self.max_hits = max_hits 
     self.timeout = timeout 
     self.cache = {} 

    def __call__(self, *args): 
     # Here the code returning the correct thing. 

def cache_hits(max_hits=10, timeout=5): 
    def _cache(function): 
     return Cache(function,max_hits,timeout) 
    return _cache 

@cache_hits() 
def double(x): 
    return x * 2 

@cache_hits(max_hits=100, timeout=50) 
def double(x): 
    return x * 2 

PS。如果Cache類別除__init____call__之外沒有其他方法,則可以移動_cache函數中的所有代碼,並刪除Cache

+1

不明智或不...如果開發人員意外地使用@cache而不是cache(),當他們嘗試調用結果函數時會發生奇怪的錯誤。其他實現實際上同時作爲緩存和緩存() – lunixbochs

+0

感謝@unutbu,也是很好的解決方案。 – Dachmt

+1

@lunixbochs:將'cache_hits'(nee'cache')與'cache_hits()'混淆的開發者可能會將任何函數對象與函數調用混淆,或者將生成器與迭代器錯誤。即使是經驗豐富的Python程序員也應該注意差異。 – unutbu

0

我從這個問題中學到了很多,謝謝大家。是不是隻是在第一個@Cache上放置了空括號?然後您可以將function參數移動到__call__

class Cache(object): 
    def __init__(self, max_hits=10, timeout=5): 
     self.max_hits = max_hits 
     self.timeout = timeout 
     self.cache = {} 

    def __call__(self, function, *args): 
     # Here the code returning the correct thing. 

@Cache() 
def double(x): 
    return x * 2 

@Cache(max_hits=100, timeout=50) 
def double(x): 
    return x * 2 

雖然我認爲這種做法是更簡單,更簡潔:

def cache(max_hits=10, timeout=5): 
    def caching_decorator(fn): 
     def decorated_fn(*args ,**kwargs): 
      # Here the code returning the correct thing. 
     return decorated_fn 
    return decorator 

如果您使用的裝飾時,忘記了括號,不幸的是你還沒有得到一個錯誤,直到運行時,如外裝飾參數傳遞你正在試圖裝飾的功能。然後在運行時內裝飾抱怨:

TypeError: caching_decorator() takes exactly 1 argument (0 given).

但是你能趕上這一點,如果你知道你的裝飾器的參數永遠不會是一個可調用:

def cache(max_hits=10, timeout=5): 
    assert not callable(max_hits), "@cache passed a callable - did you forget to parenthesize?" 
    def caching_decorator(fn): 
     def decorated_fn(*args ,**kwargs): 
      # Here the code returning the correct thing. 
     return decorated_fn 
    return decorator 

如果現在嘗試:

@cache 
def some_method() 
    pass 

你得到一個AssertionError聲明。

在整個切線上,我遇到了這個帖子,尋找裝飾類的裝飾器,而不是裝飾的類。如果其他人也這樣做,this question是有用的。