理解装饰器之前先要理解闭包
-
闭包
定义
:在一个函数内部的函数,同时内部函数又引用了外部函数的变量。
本质
:闭包是将内部函数和外部函数的执行环境绑定在一起的对象。
优点
:内部函数可以使用外部变量。
缺点
:外部变量一直存在于内存中,不会在调用结束后释放,占用内存。
作用
:实现python装饰器。 -
闭包三要素:
– 必须有一个内嵌函数。
– 内嵌函数必须引用外部函数中变量。
– 外部函数返回值必须是内嵌函数。 -
闭包函数的调用方式:
变量 = 外部函数名(参数)
变量(参数) -
函数名变量存储着函数内存地址
def test(): print("这是test函数") print(test) # <function test at 0x000001D2BE18C1E0> test() # 这是test函数
伪代码示例:
def 外部函数名(参数):
外部变量
def 内部函数名(参数):
使用外部变量
# 仅返回内部函数名(内部函数的内存地址)
return 内部函数名
代码示例:
def test(name: str):
age = 18
def my_test():
print("name:", name)
print("age:", age)
# 仅返回内部函数名(内部函数的内存地址)
return my_test
print(test) # test函数的内存地址
print(test(name="test")) # test函数内部函数my_test 函数的内存地址
result = test(name="test") # 使用变量接收test 函数返回的 my_test函数内存地址
result() # 相当于 my_test()
装饰器
而装饰器decorators,正式利用了闭包的特性
-
定义
在不改变原函数的调用以及内部代码情况下,为其添加新功能的函数。
-
伪代码示例
def 函数装饰器名称(func): 需要添加的新功能 def 内嵌函数名(*args, **kwargs): 需要添加的新功能 return func(*args, **kwargs) return 内嵌函数名
-
装饰器的使用
使用@符号,直接在函数上方添加装饰器@函数装饰器名称 想要装饰的函数()
-
本质:
使用“@函数装饰器名称”修饰原函数,等同于创建与原函数名称相同的变量,关联内嵌函数;故调用原函数时执行内嵌函数。
原函数名称 = 函数装饰器名称(原函数名称)
-
代码示例
- 定义两个普通函数,实现打招呼,与说再见功能
def say_hello(): print("你好") def say_goodbye(): print("再见")
- 由于需求变更,要求打印函数自身名字
def say_hello(): print(say_hello.__name__) print("你好") def say_goodbye(): print(say_goodbye.__name__) print("再见")
- 使用装饰器理念,变换函数
# 提取处通用点: print(函数名.__name__), 改造为装饰器 def print_func_name(func): # 函数名作为参数传递 def wrapper(*args, **kwargs): # 包装函数 print("函数名为:", func.__name__) # 提供新功能 func(*args, **kwargs) # 执行原函数 return wrapper # 返回包装函数的内存地址 @print_func_name # 使用装饰器,装饰原函数 def say_hello(): print("你好") @print_func_name def say_goodbye(): print("再见") say_hello() # 执行函数 say_goodbye()
-
示例代码解析
@print_func_name def say_hello(): print("你好") say_hello()
Step1 : 函数由上至下执行,遇到print_func_name, say_hello函数,在内存中开辟栈帧,存储函数
Step2 : 程序遇到了执行函数say_hello(),调用栈帧中的的函数
Step3 : @方法使其变为:say_hello = print_func_name(say_hello)
形式来执行函数
Step4 : 遇到wrapper 函数,又开辟一块栈帧,接着return wrapper
Step5 :say_hello = wrapper
-->say_hello() => wrapper()
Step6 :执行 wrapper函数:print("函数名为:", func.__name__)
,并且执行 传递的参数func,也就是say_hello,say_hello(*args, **kwargs)栈帧
: 每个栈帧对应着一个未运行完的函数。栈帧中保存了该函数的返回地址和局部变量
多重装饰器
一个函数可以被多个装饰器修饰, 多重装饰器,即多个装饰器修饰同一个函数对象
,且执行顺序为从近到远
@装饰器1
@装饰器2
函数名()
执行顺序为 : 函数名= 装饰器1( 装饰器2(函数名) )
先执行 装饰器2(函数名) 将返回结果作为参数 传递为装饰器1,函数1 再将结果返回
来看一段伪代码示例:
- 场景 : 编写一函数视图,必须登录才可以访问该视图,并且执行该视图时,打印出该函数名称
# 打印函数名称
def print_func_name(func1):
def wrapper(*args, **kwargs):
print("函数名为:", func.__name__)
func1(*args, **kwargs)
return wrapper # 返回包装函数的内存地址
# 登录要求装饰器
def my_login_required(func2):
def decorated_view(*args, **kwargs):
print("my_login_required开始调用")
return func2(*args, **kwargs)
return decorated_view
@my_login_required
@print_func_name
def my_view():
print("视图函数开始调用")
ps: 在编程时,通常需要对视图函数做出权限管理,比如说需要先登录才能访问某视图函数,不只是此函数需要此功能,其他函数也需要,因此提取出共同点变为装饰器,用时添加即可
问题思考:
前面提到过,
多重函数执行顺序为从近到远
,要求是:先登录才能访问该函数
,那么为什么@print_func_name
在@my_login_required
之前?而不是@print_func_name
@my_login_required
def my_view():
print(“视图函数开始调用”)
捋顺思路:
- 执行结果
my_login_required开始调用 函数名为: my_view 视图函数开始调用
- 执行过程
- 项目由上至下执行,开辟栈帧存储函数
- 遇到真正函数执行体my_view(),开始执行函数
- 由于装饰器存在,并且按照规则 从近到远 来解析
- 所以先执行print_func_name
- func1 = my_view
- 开辟栈帧存储wrapper函数
- return wrapper
- 将wrapper作为参数,传递给my_login_required
- func2 = wrapper
- 开辟栈帧存储decorated_view函数
- return decorated_view
- 此时my_view() = decorated_view(),执行decorated_view()
打印结果
: my_login_required开始调用- return wrapper(*args, **kwargs)
- 执行 wrapper(*args, **kwargs) 函数
打印结果
: 函数名为: my_view- 执行wrapper(*args, **kwargs) 函数 函数中的func 1函数
- 由于func1 = my_view(),所以执行my_view()函数
打印结果
: 视图函数开始调用
得出结论:
由先近后远原则,函数先执行靠近被函数,并将返回结果传递给上一层装饰器。
即:多重装饰器存在时,功能实现顺序: 先远后近
多层函数装饰器
来看示例:
- 普通装饰器
def print_func_name(func):
def wrapper(*args, **kwargs):
print("函数名为:", func.__name__)
func(*args, **kwargs)
return wrapper
- 多层装饰器
# 3层函数装饰器
def out_test(*args):
def test(func):
def in_test(*args, **kwargs):
print("in_test 被调用")
return func(*args, **kwargs)
return in_test
return test
当普通装饰器满足不了需求时,需要多层装饰器,对函数进一步封装
代码示例:
def print_func_name(func): # 函数名作为参数传递
def wrapper(*args, **kwargs): # 包装函数
print("函数名为:", func.__name__) # 提供新功能
func(*args, **kwargs) # 执行原函数
return wrapper # 返回包装函数的内存地址
def out_test(*args):
def test(func):
def in_test(*args, **kwargs):
print("in_test 被调用")
return func(*args, **kwargs)
return in_test
return test
@print_func_name # 使用装饰器,装饰原函数
def say_hello():
print("你好")
@out_test() # 多层函数装饰器调用
def say_goodbye():
print("再见")
say_hello() # 执行函数
say_goodbye()
结果:
函数名为: say_hello
你好
in_test 被调用
再见
函数从上至下执行
@out_test()执行到此处,发现了函数调用 : out_test()
执行out_test(),return test
解析test函数,打印 test 被调用,将test视为装饰器函数,等待被调用
再看示例:
# 代码上同,将say_goodbye函数改为 多重 + 多层函数装饰器
@print_func_name
@out_test() # 多层函数装饰器调用
def say_goodbye():
print("再见")
结果如下:
函数名为: in_test
in_test 被调用
再见
或者:
@out_test() # 多层函数装饰器调用
@print_func_name
def say_goodbye():
print("再见")
运行结果:
in_test 被调用
函数名为: say_goodbye
再见
由此可见:
多重 + 多层函数装饰器存在时,将多层函数装饰器的返回结果视为普通装饰器
让我们来看看4层函数装饰器
# 3层函数装饰器
def out_test(*args):
def test(func):
def in_test(*args, **kwargs):
print("in_test 被调用")
return func(*args, **kwargs)
return in_test
return test
# 4层装饰器
def final_test(my_array: list):
return out_test(my_array)
@final_test(my_array=[1,2,3])
def say_goodbye():
print("再见")
say_goodbye()
原理也是类似:
先执行函数,将结果返回作为普通装饰器,继续解析函数
写在最后: 如果把3层装饰器,写为普通的装饰器会这样?
3层装饰器函数 test()
装饰函数时写为 @test, [ ps :正确写法: @test()
]
答案显而易见
内部传递参数错误,报出missing required positional argument错误
或者
没有由于装饰器函数没有的正确返回结果,被装饰函数未执行
创作不易,如您感觉有收获,点个赞呗!~