一、前言
学习装饰器之前,我们先了解下闭包。
有时候我们想读取函数内部的变量,该怎么实现呢?如下
def wrapper():
n=100
print(n)
结果:NameError: name 'n' is not defined
很明显报错了,那我们要实现这个需求该怎么做呢?用闭包
闭包:能够读取外部函数内的变量的函数,如下
def wrapper():
n=100
def inner():
print(n)
inner函数就是闭包,它能够读取wrapper函数内部的n变量
我们怎么执行上面程序来验证inner确实可以读取wrapper函数内部的n变量?
def wrapper():
n=100
def inner():
print(n)
return inner
test=wrapper()
test()
结果:100
我们发现确实是可以读取wrapper函数内部的n变量,并且wrapper()是一个特殊的变量,可以赋给另一个变量。因此我们可以引申到一个叫“装饰器”的概念
二、装饰器
假如我们有个需求如下:
要求每个函数执行之前记录一次日志,每个函数执行之后记录一次日志,我们可以怎么实现呢?
def open_web():
print("打开网页......")
def fun1():
print("执行前日志......")
open_web()
print("执行后日志......")
这样看视乎没问题,但是我们有十几个、几百个函数呢?有没有更优雅的写法?这时候就需要用到装饰器了
装饰器雏形:
def open_web():
print("打开网页......")
def input():
print("输入账号,密码......")
def wrapper(fun):
def inner():
print("执行前日志......")
fun()
print("执行后日志......")
return inner
#wrapper()返回inner变量,open_web()就等于执行inner()函数
open_web=wrapper(open_web)
open_web()
结果:
执行前日志......
打开网页......
执行后日志......
这样看视乎没啥差别啊,python给我们提供了个语法糖
@wrapper
def fun()
=>等同于 fun=wrapper(fun)
wrapper()赋给fun变量,fun()就等于执行wrapper内的inner()函数,因此可以改造为如下:
def wrapper(fun):
def inner():
print("执行前日志......")
fun()
print("执行后日志......")
return inner
@wrapper
def open_web():
print("打开网页......")
@wrapper
def input():
print("输入账号,密码......")
open_web()
print("-"*12)
input()
结果:
执行前日志......
打开网页......
执行后日志......
------------
执行前日志......
输入账号,密码......
执行后日志......
如果被装饰的函数有返回值呢?
def wrapper(fun):
def inner():
print("执行前日志......")
res=fun() #如果被装饰函数有返回值则赋值给res,返回
print("执行后日志......")
return res
return inner
如果被装饰的函数有些有参数,有些不用参数呢?,那么我们可以写一个通用装饰器
def wrapper(fun):
def inner(*args,**kwargs): #组装动态参数
print("执行前日志......")
res=fun(*args,**kwargs) #拆包动态参数
print("执行后日志......")
return res
return inner
@wrapper
def open_web(url='www.baidu.com'):
print(f"打开网页{url}......")
return "百度"
@wrapper
def input(username="xian",password='123'):
print(f"输入账号{username},密码{password}......")
open_web_res=open_web()
print("-"*12)
input_res=input()
print(open_web_res)
print(input_res)
结果如下:
执行前日志......
打开网页www.baidu.com......
执行后日志......
------------
执行前日志......
输入账号xian,密码123......
执行后日志......
百度
None
装饰器需要传参,又怎么写?需要在装饰器外面再套一层
def outer(*args,**kwargs):
print(f"传进来的为",*args,**kwargs)
def wrapper(fun):
def inner(*args,**kwargs): #组装动态参数
print("执行前日志......")
res=fun(*args,**kwargs) #拆包动态参数
print("执行后日志......")
return res
return inner
return wrapper
'''
1.@outer(),会先执行outer(),outer()函数return wrapper
2.@wrapper就变成原来的装饰器
3.@wrapper
def open_web 变成open_web= wrapper(open_web)
4.所以open_web()变成执行inner函数,即经过装饰的open_web()函数
''''
@outer("测试")
def open_web(url='www.baidu.com'):
print(f"打开网页{url}......")
return "百度"
三、总结
我们每次写装饰器,都可以写如下通用装饰器:
def wrapper(fun):
@wraps(fun)
def inner(*args,**kwargs): #组装动态参数
print("执行前操作......")
res=fun(*args,**kwargs) #拆包动态参数
print("执行后操作......")
return res
return inner