Python装饰器详解

@author StormMa
@date 2017-06-09


生命不息,奋斗不止!

前言

第一次接触Python的时候就是直接开始写一些爬虫或者是使用django写简单的web应用,但是最近想回头来看看Python中好玩的特性或者是基础。今天就来看一下装饰器的使用,其实感觉Python中的装饰器,有点类似于Java中的Aspect。就像你在一个方法中处理主要的业务逻辑,如果要想在处理业务逻辑之前打印一下日志的话,那么可以使用装饰器来装饰一下原函数,在此要说明一下,这里的装饰器和Java中的装饰器模式原理上还是相通的,但是Java中的装饰器模式是对对象的增强,如果使用Java装饰器模式实现,调用也要修改,但是Python中的这种实现之前的调用方式就不用做修改。

小例子

首先,我们用一个简单的示例来展示一下装饰器的真实面貌,就用我们之前说的增加一个打印日志的功能来装饰一个函数吧,我相信这个例子你一定可以看懂。

1
2
3
4
5
6
7
8
9
10
11
12
def log(func):
def wrapper(*args, **kwargs):
print("执行%s之前" % (func.__name__))
func()
print("执行%s之后" % (func.__name__))
return wrapper
@log
def handle():
print("处理主要的业务逻辑")
if __name__ == '__main__':
handle()

上面的执行接口就是在打印handle里面的内容之前和之后分别打印了log中的日志内容。是不是就像是Java中Aspect的实现结果。我们来分析一下这个代码吧,这个代码比普通的函数调用就多了一个@log的annotation,其实@log翻译过来就是 handle = log(handle), 所以此时handle这个变量指向的不是原来的handle指向的函数,而是log返回的wrapper函数,我们原来的函数handle已经赋值给func这个变量了,此时的指向是这样的: handle –> wrapper, func –> 原来的handle指向的函数。那么此时调用handle,就是执行wrapper函数,wrapper函数中执行func就是原来的handler函数,这样就达到增强函数的效果了,其实也就是装饰器(这个名字叫的真不错,嘿哈)!其实注解这个语法就是把一个函数当做参数传递,然后回调函数的一个套路。

带参数的装饰器

如果我要在打印之前传递一个参数怎么办呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import time
def log(time):
def decorator(func):
def wrapper(*args, **kwargs):
print(time.title())
print("执行%s之前" % (func.__name__))
func()
print("执行%s之后" % (func.__name__))
return wrapper
return decorator
@log(time=time.ctime())
def handle():
print("处理主要的业务逻辑")
if __name__ == '__main__':
handle()

读者可以根据我前面对那个小例子的分析来分析这段代码。假设你心里面已经有数了。只要我们知道@log(time=xxxx)这个东西到底怎么解释就解决了所有的疑惑。这次的@log(time=xxxx)解释成 handle = log(time=xxxx)(handle),handle指向的还是wrapper最后回调的还是func指向的原来的handle指向的函数,我觉得我们理解成log(time=xxxx)返回的是decorator,然后@log(time=xxxx)等价于@ decorator这个了,然后剩下就和上面那个例子一样了,如果对变量指向函数疑惑的同学,可以打印一下这两个例子中handle的name的属性,你会发现它们的结果是一样的都是wrapper,所以结果就是

1
2
3
4
Fri Jun 9 09:43:41 2017
执行handle之前
处理主要的业务逻辑
执行handle之后