Python装饰器详解
1.闭包函数
在看装饰器之前,我们先来搞清楚什么是闭包函数。python是一种面向对象的编程语言,在python中一切皆对象,这样就使得变量所拥有的属性,函数也同样拥有。
这样我们就可以理解在函数内创建一个函数的行为是完全合法的。
下面是一个简单的闭包例子:
# 定义一个函数
def test(number_out):
# 在函数内部再定义一个函数,并且这个函数用到了外边函数的变量,那么将这个内部函数以及用到的一些变量称之为闭包
def test_in(number_in):
print("number_out:%d" % number_out)
print("in test_in 函数, number_in is %d" % number_in)
return number_out+number_in
# 其实这里返回的就是闭包的结果
return test_in
# 给test函数赋值,这个20就是给参数number_out
ret = test(20)
# 注意这里的100其实给参数number_in
print(ret(100))
运行结果:
闭包:即两个函数嵌套,外部函数返回内部函数的引用,外部函数一定会传入参数,外部函数起的是交换引用的作用.
闭包从语法上看非常简单,但是却有强大的作用。闭包可以将其自己的代码和作用域以及外部函数的作用结合在一起。
总结:什么函数可以被称为闭包函数呢?主要是满足两点:函数内部定义的函数;引用了外部变量但非全局变量。
2.什么是装饰器?
有了闭包函数的概念,我们再去理解装饰器会相对容易一些。python装饰器本质上就是一个函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外的功能,装饰器的返回值也是一个函数对象。装饰器函数的外部函数传入我要装饰的函数名字,返回经过修饰后函数的名字;内层函数(闭包)负责修饰被修饰函数。从上面这段描述中我们需要记住装饰器的几点属性,以便后面能更好的理解:
实质: 是一个函数
参数:是你要装饰的函数名(并非函数调用)
返回:是装饰完的函数名(也非函数调用)
作用:为已经存在的对象添加额外的功能
特点:不需要对对象做任何的代码上的变动
3.装饰器的作用
装饰器的作用: python装饰器就是用于拓展原来函数功能的一种函数,这个函数的特殊之处在于它的返回值也是一个函数,使用python装饰器的好处就是在不用更改原函数的代码前提下给函数增加新的功能。
一般而言,我们要想拓展原来函数代码,最直接的办法就是侵入代码里面修改,但这种方法有弊端,因为修改原代码不能保证原代码的其他模块功能的有效性,而使用装饰器能完美解决这一问题。
python装饰器有很多经典的应用场景,比如:插入日志、性能测试、事务处理、权限校验等。装饰器是解决这类问题的绝佳设计。并且从引入中的列子中我们也可以归纳出:装饰器最大的作用就是对于我们已经写好的程序,我们可以抽离出一些雷同的代码组建多个特定功能的装饰器,这样我们就可以针对不同的需求去使用特定的装饰器,这时因为源码去除了大量泛化的内容而使得源码具有更加清晰的逻辑。
4.几种常用装饰器
4.1 函数装饰器
我们以函数添加计时功能为例,讲述函数装饰器。
import time
def decorator(func):
def wrapper(*args, **kwargs):
start_time = time.time()
func()
end_time = time.time()
print(end_time - start_time)
return wrapper
@decorator # @decorator 相当于 test = decorator(test)
def test():
time.sleep(0.8)
print("test源代码")
# 函数调用
test()
在上面代码中 test是我要装饰器的函数,我想用装饰器显示test函数运行的时间。@decorator这个语法相当于 执行 test= decorator(test),为test函数装饰并返回。
再来看一下我们的装饰器函数 decorator,该函数的传入参数是func (即被装饰函数test的引用),返回参数是内层函数。这里的内层函数wrapper,其实就相当于闭包函数,它起到装饰给定函数的作用。
4.2 被装饰的函数有不定长参数
import time
def timefun(func):
def wrapped_func(*args, **kwargs):
print("%s called at %s" % (func.__name__, time.ctime()))
func(*args, **kwargs)
return wrapped_func
@timefun
def foo(a, b, c, d="foo原代码"):
time.sleep(0.8)
print(a + b + c)
print(d)
foo(3, 5, 7)
运行结果:
wrapper参数为*args, **kwargs。 *args表示的参数以列表的形式传入;**kwargs表示的参数以字典的形式传入:
从图中我们可以看到:凡是以key=value形式的参数均存在kwargs中,剩下的所有参数都以列表的形式存于args中。这里要注意的是:为了不破坏原函数的逻辑,我们要保证内层函数wrapper_func和被装饰函数func的传入参数和返回值类型必须保持一致。
4.3. 类装饰器
前面我们提到的都是让 函数作为装饰器去装饰其他的函数或者方法,那么可不可以让 一个类发挥装饰器的作用呢?答案肯定是可以的,python中一切皆对象,函数和类本质没有什么不一样。
class Decorator(object):
def __init__(self, f):
self.f = f
def __call__(self):
print("decorator start")
self.f()
print("decorator end")
@Decorator
def func():
print("func")
func()
这里值得注意的是:__call__()是一个特殊方法,它可将一个类实例变成一个可调用对象:
func = Decorator(func) # func是类Decorator的一个实例
func() # 实现了__call__()方法后,func可以被调用
要使用类装饰器必须实现类中的__call__()方法,就相当于将实例变成了一个方法。