2010-01-08 74 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)