闭包与装饰器
闭包:
python的一种独有写法,可以实现:对外部函数的局部变量进行’临时’存储
作用:
可以"延长"函数内 局部变量的生命周期.
构成条件:
1. 有嵌套. 外部函数内要嵌套 内部函数.
2. 有引用. 在内部函数中使用 外部函数的变量.
3. 有返回. 在外部函数中, 返回 内部函数名, 即: 内部函数对象
格式
def 外部函数名(形参列表):
......
def 内部函数名(形参列表): # 有嵌套
......
使用外部函数的变量 # 有引用
return 内部函数名 # 有返回
代码举例:
# 需求1: 定义1个函数用于保存变量10, 然后调用函数返回值并重复累加数值, 观察结果.
# 1. 定义函数.
def fn():
return 10
# 需求2: 定义1个用于求和的闭包, 外部函数有num1参数, 内部函数有num2参数, 然后调用, 求两数之和, 观察结果.
def fn_outer(num1): # 外部函数
def fn_inner(num2): # 内部函数, 有嵌套
# 具体的求和动作.
sum = num1 + num2 # 有引用, 内部函数中, 使用了外部函数的变量 num1
# 打印结果.
print(f'求和结果: {sum}')
return fn_inner # 有返回
# 在main函数中测试.
if __name__ == '__main__':
# 3.测试: 非闭包写法.
print(fn() + 1) # 11
print(fn() + 1) # 11
print(fn() + 1) # 11
print('-' * 21)
# 4. 测试: 闭包的写法.
my_fn = fn_outer(10)
my_fn(1) # 11
my_fn(1) # 11
my_fn(1) # 11
这里每次输出都是一样的结果,所以下面介绍一个关键字从而实现在 内部函数中, 修改外部函数的变量值.
"""
nonlocal介绍:
概述:
它可以实现在 内部函数中, 修改外部函数的 变量值.
用法:
类似于 global 关键字.
"""
# 1. 定义函数, 实现 内部函数访问外部函数的变量值.
def fn_outer(): # 外部函数
a = 100 # 外部函数的局部变量
def fn_inner(): # 内部函数, 有嵌套.
# 在内部函数中, 修改外部函数的变量值, 需要通过 nonlocal 关键字实现.
nonlocal a
a = a + 1
# 在内部函数中, 访问外部函数的变量
print(f"a: {a}") # 有引用
return fn_inner # 有返回
# 在main函数中测试调用.
if __name__ == '__main__':
fn = fn_outer() # fn = fn_inner 等价于 内部函数.
fn() # 101
fn() # 102
fn() # 103
装饰器:
装饰器 = 闭包 +额外功能
在不改变原有函数的基础上,对其功能做增强
1.有嵌套
2.有引用
3.有返回值
4.有额外功能
格式:
def 外部函数名(形参列表):
...
def 内部函数名(形参列表): # 有嵌套
功能扩展 # 有功能扩展
...
使用外部函数的变量 # 有引用
return 内部函数名 # 有返回
用法:
1.传统用法:
变量名 = 装饰器名(被装饰的函数名)
变量名()
-
@标记符实现:
在要被装饰的函数上,写:@装饰器名
代码举例:
需求: 发表评论前, 需要先登录. 大白话翻译: 定义发表评论的函数, 在不改变该函数的基础上, 实现: 先登录, 后发表评论.
# 被装饰的函数: 发表评论.
# 装饰器: 登陆功能.
# 1. 定义函数, 充当装饰器, 用来给函数增加: 登陆功能(额外功能).
def check_login(fn_name):
def fn_inner(): # 有嵌套
print('登陆中...') # 有额外功能
fn_name() # 有引用
return fn_inner # 有返回
# 2. 定义函数, 表示: 发表评论.
@check_login
def comment():
print('发表评论!...')
# 在main函数中调用.
if __name__ == '__main__':
# 3. 普通写法, 直接调用函数.
# comment()
# 4. 装饰器, 用法1: 传统格式.
# comment = check_login(comment)
# comment()
# 5. 装饰器, 用法2: 语法糖格式.
comment()
在创建装饰器的时候,需要考虑传入函数的参数问题.内部函数要和传入函数的参数一样:
如:
传入函数有参有返回值,内部函数也要有参有返回值
传入函数有参无返回值,内部函数也要有参无返回值
传入函数无参有返回值,内部函数也要无参有返回值
传入函数无参无返回值,内部函数也要无参无返回值
因为每个装饰器只允许传入一个参数,所以每当需要增加参数传入时,需要新增一层装饰器来修饰当前装饰器:
# 需求: 定义1个既能装饰加法运算, 也能装饰减法运算的装饰器, 即: 带有参数的装饰器, 并测试.
# 1. 定义装饰器, 能装饰 加法, 减法运算.
def logging(flag):
def decorator(fn_name):
# def decorator(fn_name, flag): # decorator: 装饰的意思, 这行代码会报错, 因为装饰器的参数只能有1个.
def fn_inner(): # 有嵌套
if flag == '+':
print('[友好提示] 正在努力计算 加法 中...') # 有额外功能
elif flag == '-':
print('[友好提示] 正在努力计算 减法 中...') # 有额外功能
fn_name() # 有引用
return fn_inner # 有返回
return decorator # 有返回
# 2. 定义原函数, 表示: 加法运算.
@logging('+')
def add():
a, b = 10, 3
sum = a + b
print(f'求和结果: {sum}')
# 3. 定义原函数, 表示: 减法运算.
@logging('-')
def subtract():
a, b = 22, 11
sub = a - b
print(f'求差结果: {sub}')
# 在main函数中测试调用.
if __name__ == '__main__':
# 4. 测试加法.
add()
print('-' * 21)
# 5. 测试减法.
subtract()
当多个装饰器装饰一个函数时,参考:传统写法:由内到外,语法糖:由上到下:
# 需求: 发表评论前, 需要先登录, 再进行验证码校验.
# 1. 定义装饰器, 增加 登陆功能.
def check_login(fn_name):
def fn_inner(): # 有嵌套
print('登陆校验中!.....') # 有额外功能.
fn_name() # 有引用
return fn_inner # 有返回
# 2. 定义装饰器, 增加 验证码校验功能.
def check_code(fn_name):
def fn_inner(): # 有嵌套
print('验证码校验中!.....') # 有额外功能.
fn_name() # 有引用
return fn_inner # 有返回
# 3. 定义原函数, 表示: 发表评论.
@check_login
@check_code
def comment():
print("发表评论!")
# 在main函数中, 测试.
if __name__ == '__main__':
# 4. 测试: 装饰器的写法1 传统写法.
cc = check_code(comment) # 增加: 验证码校验功能.
cl = check_login(cc) # 增加: 登陆功能.
cl()
print('-' * 21)
# 5. 测试: 装饰器的写法2 语法糖.
comment()