2010-01-08 98 views
2

我以爲我理解裝飾,但不再了。裝飾者只在創建函數時才工作嗎?Python:非常困惑裝飾者

我想創建一系列函數,它們都有一個名爲'ticket_params'的必需參數,它是一個字典。然後用@param_checker(['req_param_1', 'req_param_2'])之類的東西裝飾它們,然後如果'req_param_1'和'req_param_2'不在字典中,請引發自定義的Exception子類。我是否認爲這一切都錯了?

這將是這樣的調用代碼:

@param_checker(['req_param_1', 'req_param_2']) 
def my_decorated_function(params): 
    # do stuff 

params = {'req_param_1': 'Some Value'} 
my_decorated_function(params) 

# exception would be raised here from decorator. 

回答

11

一個裝飾器應用在def聲明後面;等價是:

@param_checker(['req_param_1', 'req_param_2']) 
def my_decorated_function(params): 
    # do stuff 

正是同樣的事情:

def my_decorated_function(params): 
    # do stuff 
my_decorated_function = param_checker(['req_param_1', 'req_param_2'])(my_decorated_function) 

所以param_checker工作是返回一個函數,作爲它的參數被裝飾功能和返回仍然另一個功能是做你所需要的。好,到目前爲止?

編輯:那麼,這裏是一個實現...:

import functools 

def param_checker(reqs): 
    reqs = set(reqs) 
    def middling(f): 
    @functools.wraps(f) 
    def wrapper(params): 
     missing = reqs.difference(params) 
     if missing: 
     raise TypeError('Missing parms: %s' % ', '.join(sorted(missing))) 
     return f(params) 
    return wrapper 
    return middling 
+1

有時我認爲我對Python中的某些東西有相當好的理解,然後Alex *解釋了它,並且我意識到我突然不瞭解它。我現在不得不在週末冥想functools和肚臍皮棉的組合。 – 2010-01-09 06:00:29

+0

@Peter,另外兩個答案將接受參數的裝飾器解釋爲類 - 在'__init__'中取出參數,然後在'__call__'中裝飾函數。這也可以,如果真實情況(arg-taking裝飾器是返回高階函數的函數 - 具體而言:返回一個函數並返回一個函數;-)感覺太奇怪了;-)。但上面的例子真的很簡單('functools.wraps'只是一個整潔的方式來保留裝飾函數的名稱和文檔字符串,以免未來內省...!)。 – 2010-01-09 06:52:40

+0

my_decorated_function = param_checker(['req_param_1','req_param_2'])(my_decorated_function)。我不明白這一點。將聲明後的裝飾函數放置在parens中以及接下來如何響應函數調用會發生什麼? – orokusaki 2010-01-09 17:30:44

5

裝飾只在一個函數調用一次,就是當def語句解析像這樣:

@mydecorator 
def myfunction(): ... 

我認爲你的意思是這樣的:

class param_checker: 
    def __init__(self, l): 
    self.l = l 

    def __call__(self, functionToBeDecorated): 
    def wrapper(*args, **kwargs): 
     if any(necessary not in kwargs["ticket_params"] for necessary in self.l): 
     raise MyCustomException 
     return functionToBeDecorated(*args, **kwargs) 

    return wrapper 

請告訴我,如果你不不明白;)

+0

BTW在你的例子是當然更容易做一個簡單的斷言,而不是使用裝飾的。 – AndiDog 2010-01-08 23:51:41

+0

這是否意味着每次調用函數時都會動態地重寫該函數? – orokusaki 2010-01-09 17:28:38

+0

我不明白如何返回'包裝'函數對象調用任何東西。這是因爲我們不需要調用它,因爲它正在被調用?之前,我會想象返回包裝器(),但我現在可能會理解。另外,如何做一個斷言,而不是。感謝這個啓發性的答案,因爲我沒有意識到你可以基本上覆蓋函數上的__call __()。 – orokusaki 2010-01-09 17:39:46

3

這裏的基礎上@ AndiDog的例子一個完整的例子。記住任何可調用的可以用作裝飾器,它不一定是一個類。

class MyCustomException(Exception): 
    pass 

# The decorator - instances of this class are callable as it implements __call__ 
class param_checker: 
    # In this example l is the parameter you pass to the decorator. 
    # For example, l could be ['req_param_1', 'req_param_2']. 
    def __init__(self, l): 
     self.l = l 

    # This makes the instance callable 
    def __call__(self, functionToBeDecorated): 
     def wrapper(*args, **kwargs): 
      # For the successful call below args =() and 
      # kwargs = {'ticket_params': {'req_param_1': 'param_1', 'req_param_2': 'param_2'}} 
      if "ticket_params" not in kwargs or any(necessary not in kwargs["ticket_params"] for necessary in self.l): 
       # if the ticket params parameter has not been specified, or if 
       # any of the required parameters are not present raise an exception 
       raise MyCustomException 
      return functionToBeDecorated(*args, **kwargs) 
     return wrapper 

@param_checker(['req_param_1', 'req_param_2']) 
def myfunction(ticket_params=None): 
    # if the two required params are present this will print 
    print "params ", ticket_params 

if __name__ == "__main__": 
    try: 
     myfunction() 
    except MyCustomException: 
     print "all required params not supplied" 
    try: 
     myfunction(ticket_params={'req_param_1': 'param_1'}) 
    except MyCustomException: 
     print "all required params not supplied" 
    myfunction(ticket_params={'req_param_1': 'param_1', 'req_param_2': 'param_2'}) 
+0

非常感謝約翰。我最終接受了Alex的回答,因爲他幫助我理解了基本的函數裝飾器,但Andi的回答提出了問題,即您的答案對我有用。儘管我把所有3個答案都投了。 – orokusaki 2010-01-09 17:47:58

+0

看來這篇文章self.l實際上是函數對象本身:http://www.artima.com/weblogs/viewpost.jsp?thread=240808 – orokusaki 2010-01-09 17:51:08

+0

我也接受亞歷克斯的,我不是'真的回答這個問題,只是想給你一些工作代碼來玩。 不確定第二條評論的意思。 – 2010-01-09 23:24:36

2

檢查Alex的答案,以瞭解Python裝飾器;順便說一句:

1)你不瞭解裝飾者?難道你不把裝飾器理解爲一般概念,還是Python裝飾器?請注意,「古典」裝飾模式,Java註釋和python裝飾器是不同的東西。

2)python裝飾器應該總是返回一個函數,例如在你的代碼中,param_checker([...])的返回值應該是一個函數,它接受一個函數作爲參數(要被修飾的函數),並返回一個與my_decorated_function具有相同簽名的函數。看看下面的例子;裝飾器函數只執行一次(創建類時),而裝飾的函數隨後在每次調用時執行。在這個特定的例子中,它然後調用原始的func,但這不是必需的。

def decorator(orig_func): 
    print orig_func 

    def decorated(self, a): 
     print "aahahah", orig_func(self, a) 

    return decorated 


class Example(object): 
    @decorator 
    def do_example(self, a): 
     return 2 * a 


m = Example() 
m.do_example(1) 

3)你可能沒有像你使用裝飾器那樣做最好的事情。當一些概念與你實際編程的內容非常正交時,通常應該使用它們,並且可以重複使用它 - 它本質上就是Python做AOP的方式。你的param_checker可能不是那個正交的 - 如果你的裝飾器只使用一次,那麼它可能根本不是一個使用裝飾器的好地方。您的param_checker似乎是這種情況 - 它假定裝飾的func採用單個arg這是一本字典 - 您的代碼中是否有許多funcs具有這樣的簽名和行爲?如果答案是「否」,只需檢查func開始處的參數並在缺失時引發異常。

0

這是爲那些問同一個問題一個很好的解釋:

# This does nothing. 

class donothing(object): 
    def __init__(self, func): 
     """ 
     The 'func' argument is the function being decorated because in this 
     case, we're not instantiating the decorator class. Instead we are just 
     using the class object as a callable (a class is always callable as this 
     is how an instance is returned) to use as a decorator, which means that 
     it is being instantiated upon definition of the decorated function and 
     the decorated function is being passed in as an argument to the class's 
     __init__ method. 
     """ 
     self.func = func 

    def __call__(self, *args, **kwargs): 
     """ 
     The __call__ function is called when the decorated function is called 
     because the function has be eaten by the decorator class. Now it's up to 
     the this method to return a call to the original function. The arguments 
     are passed in as args, kwargs to be manipulated. 
     """ 
     # Returns original function call with original arguments. 
     return self.func(*args, **kwargs) 

@donothing 
def printer(text): 
    print(text) 

printer('hello world') 

# The printer function is now an alias for the donothing instance created, so 
# the preceding was the same as: 
# 
# instance = donothing(printer) 
# instance('hello world') 
# 


# Next example: 

class checkforkeysinparams(object): 
    def __init__(self, required): 
     self.required = set(required) 

    def __call__(self, params): 
     def wrapper(params): 
      missing = self.required.difference(params) 
      if missing: 
       raise TypeError('Missing from "params" argument: %s' % ', '.join(sorted(missing))) 
     return wrapper 


# Apply decorator class, passing in the __init__'s 'required' argument. 

@checkforkeysinparams(['name', 'pass', 'code']) 
def complex_function(params): 
    # Obviously these three are needed or a KeyError will be raised. 
    print(params['name']) 
    print(params['pass']) 
    print(params['code']) 


# Create params to pass in. Note, I've commented out one of the required params. 

params = { 
    'name': 'John Doe', 
    'pass': 'OpenSesame', 
    #'code': '1134', 
} 

# This call will output: TypeError: Missing from "params" argument: code 

complex_function(params=params)