#原创,转载请留言联系
装饰器的本质就是闭包,所以想知道装饰器是什么,首先要理解一下什么是闭包。
- 闭包
1. 外部函数返回内部函数的引用。
2. 内部函数使用外部函数的变量或者参数。
def outer(outer_num): def inner(inner_num): a = outer_num+inner_num print(a) return inner f1 = outer(1) f1(2) 输出: 3
1.outer函数返回inner函数的引用,f1=outer(1),实质就是f1=inner,而且还有,它会传入一个变量outer_num=1。这个变量是outer函数的局部变量,但是他是inner函数的“全局变量”。所以这个变量在执行完f1=outer(1)后,并不会被回收。(我们都知道普通函数里面的变量在运行完函数后会被回收。全局变量不会。)
2.inner函数使用了outer函数的变量。
所以上面那个是一个闭包!
拓展:如果要在内部函数修改外部函数的变量,不是用global,而是用nonlocal。
def outer(num): def inner(): nonlocal num num += 1 print(num) return inner f1 = outer(1) f1() f1() f1() 输出: 2 3 4
说了这么多,闭包有什么用处呢?
当你做一个项目时,当已经实现功能的代码不允许修改了!但是又想添加新功能时,这时候闭包的作用就出来了!
假设有一个这样的基础函数:
def transer(): """实现转账的函数""" print("正在转账...")
这是一个实现转账功能的函数,但是还没有身份验证等步骤,我们需要完善这个项目。但是又不能修改这个函数的源代码,这时候应该怎么办呢?没错,就是闭包。
def transer(): """实现转账的函数""" print("正在转账...") def outer(func): def vifi(): """实现身份验证的函数""" print("正在验证身份...") func() return vifi f1 = outer(transer) f1() 输出: 正在验证身份... 正在转账...
完美解决!
上面的代码又可以稍微更改成这样:
def outer(func): def vifi(): """实现身份验证的函数""" print("正在验证身份...") func() return vifi @outer # 等价于outer(transer) def transer(): """实现转账的函数""" print("正在转账...") transer() 输出: 正在验证身份... 正在转账...
@outer就是装饰器!在不改变函数的定义和调用的前提下,给函数扩展功能,这就是装饰器。
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
上面装饰器装饰的函数没有接收参数,也没有返回值。当装饰的函数接收情况或者有返回值时怎么操作呢?
1.当被装饰的函数有接收参数没有返回值的情况,装饰器应该怎么改?
def outer(func): def inner(num): # 这里要写num形参
"""我是装饰的功能""" func(num) # 这里也要写num形参 return inner @outer def normal(num):
"""我是被装饰的函数""" num += 1 print(num) normal(5) 输出: 6
2.当被装饰的函数没有接收参数有返回值的情况,装饰器应该怎么改?
def outer(func): def inner():
"""我是装饰的功能""" return func() # 这里要return函数,return的值返回给inner函数,才能接受到。 return inner @outer def normal():
"""我是被装饰的函数""" return "我是返回值" result = normal() print(result) 输出: 我是返回值
3.当被装饰的函数既有接受参数又有返回值的情况,装饰器应该怎么改?
def outer(func): def inner(num):
"""我是装饰的功能""" return func(num) # 前面两者综合 return inner @outer def normal(num):
"""我是被装饰的函数""" num += 1 print(num) return "我是返回值" result = normal(5) print(result) 输出: 6 我是返回值
综合上面各种情况,我们可以写一个万能装饰器,这样的话,就不用根据被装饰函数有没接收参数,有没返回值,而改来改去了。
def outer(func): def inner(*args,**kwargs): """我是装饰的功能""" result = func(*args,**kwargs) return result return inner @outer def normal(*args,**kwargs): """我是被装饰的函数""" print(args,kwargs) return 'something' i = normal(5) print(i) 输出: (5,) {} something
这个万能装饰器,无论被装饰函数有没接收参数,有没返回值都可以正常运行了!
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
当有多个装饰器装饰一个函数的时候,装饰的顺序是怎样的呢?
def w1(func): def inner(): return '<b>'+func()+"</b>" return inner def w2(func): def inner(): return '<t>' + func() + "</t>" return inner @w1 @w2 def transer(): return 'hello-world' print(transer()) 输出: <b><t>hello-world</t></b>
可以看出,虽然执行顺序是从外到里,但是装饰的顺序是从里到外的。
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
如果我们有一个需求,希望装饰函数时额外传一个参数flag,当这个参数是1时,就进行装饰。如果是0时,就不进行装饰。应该怎么操作呢?
有一个错误的做法经常很多人犯,如下:
def outer(func): def vifi(flag): """实现身份验证的函数""" if flag == 1: print("正在验证身份...") func() return vifi @outer def transer(): """实现转账的函数""" print("正在转账...") transer(1) print(" ") transer(0) 输出: 正在验证身份... 正在转账... 正在转账...
诶???结果不是正确了吗?不是实现这个功能了吗?但是,装饰器的定义是,在不改变函数的定义和调用的前提下,给函数扩展功能!
这样的写法,不是已经改变了函数的调用了吗?transer函数本来没有形参的,然后你无缘无故给他一个参数......
正确的写法应该是这样!通过装饰器工厂实现对装饰器传递额外的参数!
def outouter(flag): def outer(func): def vifi(): """实现身份验证的函数""" if flag == 1: print("正在验证身份...") func() return vifi return outer @outouter(1) # @outouter(0) def transer(): """实现转账的函数""" print("正在转账...") transer() 输出: 正在验证身份... 正在转账...
正确的做法是在闭包再加一层。用来接受参数。这种做法就叫装饰器工厂!
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
类装饰器。
不仅仅函数能写装饰器,类也可以,但是不常用。
class Foo(object): def __init__(self,func): self.func = func def __call__(self, *args, **kwargs): print("正在验证身份...") self.func() """Foo本质是transer=Foo(transer)。 左边的transer是调用__call__魔方方法。 右边的是创建对象,把transer这个函数名传进去,__call__魔方方法才能使用""" @Foo def transer(): print("正在转账...") transer() 输出: 正在验证身份... 正在转账...
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
说在最后:
装饰器是Python中一个很重要的东西,经常会用到,而且还意想不到的好用。
装饰器常应用于:
- 引入日志
- 函数执行时间统计
- 执行函数前预备处理
- 执行函数后清理功能
- 权限校验等场景
- 缓存