神奇的装饰器
装饰器,是Python的很重要的一个功能。如果有Java基础的童鞋会晓得,有个东西叫注解。他们是类似的东西。
python的装饰器,取决于以下几个特点:
- 函数可以作为参数
- 函数的返回值可以是参数
- python闭包特性
- 函数可以像变量一样赋值使用
首先我们来先写一个hello world
然后,我们想要在进入之前,打印一下时间。怎么办呢,我们可以这么办。def hello_world(){ print('hello the world!!') }
但是这样写没有办法通用,如果我想要在其他函数上也加上这个进入时间的话。很麻烦。我们可以利用python的函数可以作为参数传递的性质来解决这个问题。from date.datetime import datetime def inter_time() { print("进入时间:{d_time}".format(d_time=datetime.now())); hello_world(); }
我们可以通过from date.datetime import datetime def inter_time(c_func) { print("进入时间:{d_time}".format(d_time=datetime.now())); # 通过函数名加()的方式,来执行函数 c_func();
inter_time(test_func)
的方式,来实现在test_func执行之前,输出时间了。但是有个问题,我并不想把我的函数都变成inter_time(test_func)
来实现,我还是想用我自己的名字来执行(test_func()
)。我们可以通过python的闭包和函数可以最为返回值的性质来实现。
注意,inter_time的返回值是一个函数,不是一个函数执行的结果,因为它的函数名后面没有括号。你可以这样使用它from date.datetime import datetime def inter_time(c_func) { # 内函数 def show_time() { print("进入时间:{d_time}".format(d_time=datetime.now())); # 通过函数名加()的方式,来执行函数 c_func(); } return show_time
test_func = inter_time(test_func)
。这个执行结果,其实就是把test_func函数重新赋值成了show_time。这样你就可以通过执行test_func()来实现我们输出进入时间的目的了。那其实我们已经实现了我们的装饰器了,只不过这种使用方式还不是很方便。我们可以用这种方式。
大体上我们的装饰器就好了,可以用了。但是还是有些细节需要处理的。比如,你想要打印test_func.name,用来做其他处理。但是你发现,你打印出来的并不是test_func,而是show_time。这就麻烦了,怎么办呢。其实django内置了一个装饰器来解决这个问题,就是@wraps,我们修改一下我们的装饰器。# 通过@加装饰器名称,放在需要被装饰的函数前面 # 就可以实现装饰器的使用 # 等效于 test_func = inter_time(test_func) @inter_time def test_func(): pass
但是大家有没有注意,这个内置装饰器wraps它竟然带参数。哇,这么神奇,我也想要。那怎么办呢,我也带一个呗。不急,在带参数之前,我们首先试试看,装饰器带括号后,是什么情况。from date.datetime import datetime def inter_time(c_func) { # 内函数 # 内置装饰器wraps # 它做了啥呢。其实就是将c_func的内置属性,复制到了show_time上 # 这样,我们输出test_func.__name__的时候,就是test_func本身的属性值了 @wraps(c_func) def show_time() { print("进入时间:{d_time}".format(d_time=datetime.now())); # 通过函数名加()的方式,来执行函数 c_func(); } return show_time
完了,报错了。@inter_time() def test_func(): pass
TypeError: inter_time() missing 1 required positional argument: 'func'
。为什么的,我们将装饰器转换成函数调用的方式,你就明白了。带了括号的装饰器,调用过程是这样的。
对你没看错,带括号之后他会自己首先执行一次,然后在对执行结果在执行一次。那怎么办呢,我们可以在我们原有的装饰器外面,在加一层函数。test_func = inter_time()(test_func)
这个时候,我们就可以使用带有参数的装饰器了。from date.datetime import datetime def inter_time(c_from) : def the_time(func) : @wraps(func) def show_time() : print('来源:{c_from}.format(c_from=c_from)) print("进入时间:{d_time}".format(d_time=datetime.now())); # 通过函数名加()的方式,来执行函数 c_func() return show_time return the_time
最后一个问题,如果我的test_func有参数怎么办,我该怎么传过去呢。这个很简单,你只需要在装饰器里面带上参数,并使用就行了。# 相当于 test_func = inter_time('sss')(test_func) @inter_time('xxx') def test_func(): pass
好啦,你可以开始设计机自己的装饰器了from date.datetime import datetime def inter_time(c_from) : def the_time(func) : @wraps(func) def show_time(*args, **kwargs) : # 这里使用了参数 print('来源:{c_from}.format(c_from=c_from)) print("进入时间:{d_time}".format(d_time=datetime.now())); # 通过函数名加()的方式,来执行函数 # 这里使用了参数 c_func(*args, **kwargs) return show_time return the_time