说到python中的装饰器,很容易联想到设计模式中有个模式叫做装饰器模式,设计模式中的那个装饰器模式的本质其实就是装饰器类和被装饰类实现共同的接口,装饰器类中持有被装饰类的对象并且覆写被装饰类中的方法,覆写的方法中一边通过被装饰类的对象调用该对象自己的方法,一边自定义自己的装饰。python中的装饰器和上面的思想也是类似的。
1. python装饰器的一个简单实例实现
def logging(fn):
def inner():
print("被装饰函数执行之前log一下~")
fn()
print("被装饰函数执行之后log一下~")
return inner
@logging
def fun():
print("执行被装饰函数...")
if __name__ == '__main__':
fun()
其中 @logging 属于一种语法糖,作用上等价于fun = logging(fun) , @logging 根据装饰函数的函数名不同可以为 @xxx 。
2. python中装饰器的实现原理
把上面代码还原成不使用语法糖的格式也许更容易理解:
def logging(fn):
def inner():
print("装饰函数执行之前log一下~")
fn()
print("被装饰函数执行之后再log一下~")
return inner
def fun():
print("执行被装饰函数...")
if __name__ == '__main__':
fun = logging(fun)
fun()
上面说过,@logging 只是一种语法糖,目的是避免开发者自己手动的fun = logging(fun) ,繁琐而且容易出错,所以上面两段代码本质上是一样的,毫无区别。
从最后两行代码可以看出:此fun(main中的fun)非彼fun(main外的fun),二者只是同名,或者说,main外的fun已经被main中的fun覆盖了。此时的对应关系是: 1.main中的 fun <=> inner 2.main外的 fun <=> fn
最后的 fun() 其实调用的是 inner()。这,就是python中装饰器的实现原理。
3. 装饰带有不定长参数和返回值的函数实例
def logging(fn):
def inner(*args, **kwargs):
print("检查参数类型是否有误~")
return fn(*args, **kwargs)
return inner
@logging
def fun(*args, **kwargs):
result = 0
for i in args:
result += i
for i in kwargs.values():
result += i
return result
if __name__ == '__main__':
res = fun(1, 2, 3, a=4, b=5)
print(res)
这里要提别提一下如果fun为带返回值的函数,第4行fn前面的return必不可少,否则会造成print(res)的时候结果为None。原因还是因为main在执行fun(1, 2, 3, a=4, b=5) 时此时的fun已经是inner了,return fn(*args, **kwargs) 在本质上等价于return return fun(*args, **kwargs) (这里的fun指的是被装饰之前的fun)。
4. 拓展:闭包
闭包个人理解和装饰器其实就是一回事,区别在于闭包传进去的参数可以不是函数,对于接收inner对象的函数名也没有特殊要求。而装饰器要求传进去的参数必须为函数类型(就是被装饰的函数),接收inner的函数名也必须和传入的函数名相同。
下面贴一下关于闭包的确切定义: 1.在函数嵌套(函数里面再定义函数)的前提下 2.内部函数使用了外部函数的变量(还包括外部函数的参数) 3.外部函数返回了内部函数
常规思路下对方法调用的理解是:方法在被调用时入栈,执行完时出栈销毁,其中保存的变量也会随之一并销毁,但在闭包形成时,外部函数在调用完成并返回内部函数对象时,检测到内部函数使用了自己的局部变量,便会将自己所被使用到的局部变量与内部对象绑定在一起返回。
闭包的一个简单实例,实现求一个数的n次方:
def outer(n):
def inner(num):
return num ** n
return inner
if __name__ == '__main__':
fun1 = outer(3)
res1 = fun1(4)
print(f"res1:{res1}")
fun2 = outer(2)
res2 = fun2(8)
print(f"res2:{res2}")
|