python 装饰器

原文:http://docs.pythontab.com/interpy/decorators/your_first_decorator/

你的第一个装饰器:

在上一个例子里,其实我们已经创建了一个装饰器!现在我们修改下上一个装饰器,并编写一个稍微更有用点的程序:

 1 def a_new_decorator(a_func):
 2 
 3     def wrapTheFunction():
 4         print("I am doing some boring work before executing a_func()")
 5 
 6         a_func()
 7 
 8         print("I am doing some boring work after executing a_func()")
 9 
10     return wrapTheFunction
11 
12 def a_function_requiring_decoration():
13     print("I am the function which needs some decoration to remove my foul smell")
14 
15 a_function_requiring_decoration()
16 #outputs: "I am the function which needs some decoration to remove my foul smell"
17 
18 a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)
19 #now a_function_requiring_decoration is wrapped by wrapTheFunction()
20 
21 a_function_requiring_decoration()
22 #outputs:I am doing some boring work before executing a_func()
23 #        I am the function which needs some decoration to remove my foul smell
24 #        I am doing some boring work after executing a_func()

 

你看明白了吗?我们刚刚应用了之前学习到的原理。这正是python中装饰器做的事情!它们封装一个函数,并且用这样或者那样的方式来修改它的行为。现在你也许疑惑,我们在代码里并没有使用@符号?那只是一个简短的方式来生成一个被装饰的函数。这里是我们如何使用@来运行之前的代码:

 1 @a_new_decorator
 2 def a_function_requiring_decoration():
 3     """Hey you! Decorate me!"""
 4     print("I am the function which needs some decoration to "
 5           "remove my foul smell")
 6 
 7 a_function_requiring_decoration()
 8 #outputs: I am doing some boring work before executing a_func()
 9 #         I am the function which needs some decoration to remove my foul smell
10 #         I am doing some boring work after executing a_func()
11 
12 #the @a_new_decorator is just a short way of saying:
13 a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)

 

希望你现在对Python装饰器的工作原理有一个基本的理解。如果我们运行如下代码会存在一个问题:

1 print(a_function_requiring_decoration.__name__)
2 # Output: wrapTheFunction

 

这并不是我们想要的!Ouput输出应该是“a_function_requiring_decoration”。这里的函数被warpTheFunction替代了。它重写了我们函数的名字和注释文档(docstring)。幸运的是Python提供给我们一个简单的函数来解决这个问题,那就是functools.wraps。我们修改上一个例子来使用functools.wraps:

 1 from functools import wraps
 2 
 3 def a_new_decorator(a_func):
 4     @wraps(a_func)
 5     def wrapTheFunction():
 6         print("I am doing some boring work before executing a_func()")
 7         a_func()
 8         print("I am doing some boring work after executing a_func()")
 9     return wrapTheFunction
10 
11 @a_new_decorator
12 def a_function_requiring_decoration():
13     """Hey yo! Decorate me!"""
14     print("I am the function which needs some decoration to "
15           "remove my foul smell")
16 
17 print(a_function_requiring_decoration.__name__)
18 # Output: a_function_requiring_decoration

 

现在好多了。我们接下来学习装饰器的一些常用场景。

蓝本规范:

 1 from functools import wraps
 2 def decorator_name(f):
 3     @wraps(f)
 4     def decorated(*args, **kwargs):
 5         if not can_run:
 6             return "Function will not run"
 7         return f(*args, **kwargs)
 8     return decorated
 9 
10 @decorator_name
11 def func():
12     return("Function is running")
13 
14 can_run = True
15 print(func())
16 # Output: Function is running
17 
18 can_run = False
19 print(func())
20 # Output: Function will not run

 

注意:@wraps接受一个函数来进行装饰,并加入了复制函数名称、注释文档、参数列表等等的功能。这可以让我们在装饰器里面访问在装饰之前的函数的属性。

使用场景

现在我们来看一下装饰器在哪些地方特别耀眼,以及使用它可以让一些事情管理起来变得更简单。

授权(Authorization)

装饰器能有助于检查某个人是否被授权去使用一个web应用的端点(endpoint)。它们被大量使用于Flask和Django web框架中。这里是一个例子来使用基于装饰器的授权:

 1 from functools import wraps
 2 
 3 def requires_auth(f):
 4     @wraps(f)
 5     def decorated(*args, **kwargs):
 6         auth = request.authorization
 7         if not auth or not check_auth(auth.username, auth.password):
 8             authenticate()
 9         return f(*args, **kwargs)
10     return decorated

日志(Logging)

日志(Logging)日志是装饰器运用的另一个亮点。这是个例子:

 1 from functools import wraps
 2 
 3 def logit(func):
 4     @wraps(func)
 5     def with_logging(*args, **kwargs):
 6         print(func.__name__ + " was called")
 7         return func(*args, **kwargs)
 8     return with_logging
 9 
10 @logit
11 def addition_func(x):
12    """Do some math."""
13    return x + x
14 
15 
16 result = addition_func(4)
17 # Output: addition_func was called

带参数的装饰器

来想想这个问题,难道@wraps不也是个装饰器吗?但是,它接收一个参数,就像任何普通的函数能做的那样。那么,为什么我们不也那样做呢?

这是因为,当你使用@my_decorator语法时,你是在应用一个以单个函数作为参数的一个包裹函数。记住,Python里每个东西都是一个对象,而且这包括函数!记住了这些,我们可以编写一下能返回一个包裹函数的函数。

在函数中嵌入装饰器

我们回到日志的例子,并创建一个包裹函数,能让我们指定一个用于输出的日志文件。

 1 from functools import wraps
 2 
 3 def logit(logfile='out.log'):
 4     def logging_decorator(func):
 5         @wraps(func)
 6         def wrapped_function(*args, **kwargs):
 7             log_string = func.__name__ + " was called"
 8             print(log_string)
 9             # 打开logfile,并写入内容
10             with open(logfile, 'a') as opened_file:
11                 # 现在将日志打到指定的logfile
12                 opened_file.write(log_string + '\n')
13             return func(*args, **kwargs)
14         return wrapped_function
15     return logging_decorator
16 
17 @logit()
18 def myfunc1():
19     pass
20 
21 myfunc1()
22 # Output: myfunc1 was called
23 # 现在一个叫做 out.log 的文件出现了,里面的内容就是上面的字符串
24 
25 @logit(logfile='func2.log')
26 def myfunc2():
27     pass
28 
29 myfunc2()
30 # Output: myfunc2 was called
31 # 现在一个叫做 func2.log 的文件出现了,里面的内容就是上面的字符串

装饰器类

现在我们有了能用于正式环境的logit装饰器,但当我们的应用的某些部分还比较脆弱时,异常也许是需要更紧急关注的事情。比方说有时你只想打日志到一个文件。而有时你想把引起你注意的问题发送到一个email,同时也保留日志,留个记录。这是一个使用继承的场景,但目前为止我们只看到过用来构建装饰器的函数。

幸运的是,类也可以用来构建装饰器。那我们现在以一个类而不是一个函数的方式,来重新构建logit

 1 from functools import wraps
 2 
 3 class logit(object):
 4     def __init__(self, logfile='out.log'):
 5         self.logfile = logfile
 6 
 7     def __call__(self, func):
 8         @wraps(func)
 9         def wrapped_function(*args, **kwargs):
10             log_string = func.__name__ + " was called"
11             print(log_string)
12             # 打开logfile并写入
13             with open(self.logfile, 'a') as opened_file:
14                 # 现在将日志打到指定的文件
15                 opened_file.write(log_string + '\n')
16             # 现在,发送一个通知
17             self.notify()
18             return func(*args, **kwargs)
19         return wrapped_function
20 
21     def notify(self):
22         # logit只打日志,不做别的
23         pass

这个实现有一个附加优势,在于比嵌套函数的方式更加整洁,而且包裹一个函数还是使用跟以前一样的语法:

1 @logit()
2 def myfunc1():
3     pass

现在,我们给logit创建子类,来添加email的功能(虽然email这个话题不会在这里展开)。

 1 class email_logit(logit):
 2     '''
 3     一个logit的实现版本,可以在函数调用时发送email给管理员
 4     '''
 5     def __init__(self, email='admin@myproject.com', *args, **kwargs):
 6         self.email = email
 7         super(logit, self).__init__(*args, **kwargs)
 8 
 9     def notify(self):
10         # 发送一封email到self.email
11         # 这里就不做实现了
12         pass

从现在起,@email_logit将会和@logit产生同样的效果,但是在打日志的基础上,还会多发送一封邮件给管理员。

posted on 2018-04-12 11:04  Guo磊  阅读(214)  评论(0编辑  收藏  举报

导航