python第十三课---多层语法糖,有参装饰器,装饰器修复技术,装饰器的特殊使用情况,递归函数
昨日内容回顾
- global与nonlocal关键字
global
用于局部名称空间修改全局名称空间中的名字绑定关系
nonlocal
用于局部名称空间修改外层局部名称空间中的名字绑定关系
- 函数名的多种用法
1.函数名可以当做变量名被赋值
def index():
print('from index')
res = index
2.函数名可以当做另一个函数的实参
def func(a):
print(a)
func(123)
func(index)
3.函数名可以当做另一个函数的返回值
def foo():
def inner():
print('from inner')
return inner
res = foo() # res = inner
4.函数名还可以当做容器类型的数据值
func_dict = {'1':index,'2':func}
# 说白了函数名相当于一个变量,指向了一个内存地址(这个地址里保存函数体的代码)
# 所以变量怎么用,函数名就怎么用,除了函数名可以调用运行外
- 闭包函数
1.定义在函数内部的函数
2.使用了外部函数名称空间中的名字
作用:
1. 可以通过返回的变量名,实现在外部调用内部的函数体代码运行的功能!!!
2. 可以通过外面的函数往里面的函数里传参数!!!
def inner(name):
print(name)
def outer(name): # name = 'jason'
def inner():
print(name)
return inner
res = outer('jason') # res = inner
res() # inner()
- 装饰器前戏
1.不改变被装饰对象源代码
2.不改变被装饰对象调用方式
给被装饰对象添加新的功能
时间相关操作
import time
time.time()
time.sleep()
- 装饰器推导流程
1.统计函数的执行时间
def index():
print('from index')
start_time = time.time()
index()
print(time.time() - start_time)
2.由于index函数可能需要在很多地方执行 代码反复编写不好 考虑封装成函数
def get_time():
start_time = time.time()
index()
print(time.time() - start_time)
3.上述代码只能统计index函数的执行时间 局限性太强 应该让该名字动起来
def get_time(xxx):
start_time = time.time()
xxx()
print(time.time() - start_time)
4.上述直接封装形参传值不写 所以只能考虑闭包函数
def outer(xxx):
def get_time(a):
start_time = time.time()
xxx(、、、、、、、、、、、、)
print(time.time() - start_time)
return get_time
res = outer(index) # res = get_time
index = outer(index) # index = get_time
index()
5.上述装饰器只能装饰无参函数兼容性不好
def func(a):
print('from func', a)
func(123) # 正确
func = outer(func)
func(123) # 报错
推导可得被装饰的函数需要几个参数get_time就应该加几个参数
def outer(xxx):
def get_time(*args, **kwargs):
start_time = time.time()
xxx(*args, **kwargs)
print(time.time() - start_time)
return get_time
6.被装饰对象返回值无法获取
def outer(xxx):
def get_time(*args, **kwargs):
start_time = time.time()
ret = xxx(*args, **kwargs)
print(time.time() - start_time)
return ret
return get_time
func = outer(func)
res = func(123)
- 装饰器模板
def outer(func_name):
def inner(*args, **kwargs):
'''执行被装饰对象之前可以做的额外操作'''
res = func_name(*args, **kwargs)
'''执行被装饰对象之后可以做的额外操作'''
return res
return inner
- 装饰器语法糖
@outer # register = outer(register)
def register():
pass
@outer # login = outer(login)
def login():
pass
register()
login()
"""
自动将紧挨着语法糖下面的名字当做参数传递给@后面的函数并调用
"""
今日内容概要
- 作业讲解
- 多层语法糖问题
- 有参装饰器
- 装饰器修复技术
- 递归函数
- 算法之二分法
今日内容详细
作业讲解
1.编写一个用户认证装饰器
函数:register login transfer withdraw
基本要求
执行每个函数的时候必须先校验身份 eg: jason 123
拔高练习(有点难度)
执行被装饰的函数 只要有一次认证成功 那么后续的校验都通过
提示:全局变量 记录当前用户是否认证
# 定义一个变量记录用户的登录状态
is_login = False
def login_auth(func_name):
def inner(*args, **kwargs):
global is_login
# 先判断全局名称空间中的变量名is_login绑定的值是否为True
if is_login:
res = func_name(*args, **kwargs)
return res
username = input('username>>>:').strip()
password = input('password>>>:').strip()
if username == 'jason' and password == '123':
# 将全局名称空间中记录用户登录状态的数据值该为True
is_login = True
res = func_name(*args, **kwargs)
return res
else:
print('用户名或密码错误无法执行函数')
return inner
@login_auth
def register():
print('注册功能')
@login_auth
def login():
print('登录功能')
@login_auth
def shopping():
print('购物功能')
register()
login()
shopping()
.
.
.
.
.
.
多层语法糖
def outter1(func1):
print('加载了outter1')
def wrapper1(*args, **kwargs):
print('执行了wrapper1')
res1 = func1(*args, **kwargs)
return res1
return wrapper1
def outter2(func2):
print('加载了outter2')
def wrapper2(*args, **kwargs):
print('执行了wrapper2')
res2 = func2(*args, **kwargs)
return res2
return wrapper2
def outter3(func3):
print('加载了outter3')
def wrapper3(*args, **kwargs):
print('执行了wrapper3')
res3 = func3(*args, **kwargs)
return res3
return wrapper3
@outter1
@outter2
@outter3
def index():
print('from index')
index()
# 执行结果:
# 注意不运行index()函数,直接执行脚本文件,也会打印最上面这3行话的!!!
# 不要理解为,只有运行被装饰的函数index时,装饰器函数outter才会运行!!!
# @outter
# def index(): pass
# 当代码加载到这的时候,就已经自动运行该代码 index = outter(index)
# 也就是运行装饰器函数 outter() 了!!!
加载了outter3
加载了outter2
加载了outter1
执行了wrapper1
执行了wrapper2
执行了wrapper3
from index
-------------------------------------------
# 多层语法糖 加载顺序由下往上
# 执行顺序 准确讲并不是由上往下!!!
--------------------------------------------
# 执行的顺序如下:
def outter2(func2):
print('加载了outter2')
def wrapper2(*args, **kwargs):
print('执行了wrapper2')
res2 = func2(*args, **kwargs) # func2 实际上是wrapper3函数名
print('执行wrapper2 结束了')
return res2
return wrapper2
def outter3(func3):
print('加载了outter3')
def wrapper3(*args, **kwargs):
print('执行了wrapper3')
res3 = func3(*args, **kwargs) # func3 才是真正的被装饰的函数名
print('执行wrapper3 结束了')
return res3
return wrapper3
@outter1
@outter2
@outter3
def index():
print('from index')
# 注意装饰器函数 与 装饰器函数的内层函数的区别,@装饰器函数名
# 装饰器函数就已经被传参并运行了!!!
# 但是此时 装饰器的内层函数 并没有被运行!!!
# index() 函数还没调用前就已经先执行如下步骤了
# 1 先运行outer3(index函数名),并把返回值wrapper3函数名往上一层的装饰器函数里面传
# 2 再运行outer2(wrapper3函数名),并把返回值wrapper2函数名往上一层的装饰器函数里面传
# 3 由于outter1装饰器函数上面没有装饰器函数了,所以执行 index=outer1(wrapper2)
# 4 运行完outer1(wrapper2),把返回值wrapper1,赋值给了index
# 5 所以也就是在还没有运行被装饰的函数前,这4步已经走完了
# 6 最后当我们运行被装饰的函数index()时,
# 实际上就是开始从最外面的内层函数wrapper1() 开始执行了
# 7 由于装饰器函数已经从下往上加载过一遍了,每一个装饰器函数都已经被传参并运行过了
# 8 所以最外面的内层函数wrapper1() 开始执行时,func1是有明确值的,就是wrapper2函数名
# 9 同理func2也是有明确值的,就是wrapper3函数名
# 同理func3也是有明确值的,就是被装饰的函数名
index() # 执行的结果如下:
加载了outter3
加载了outter2
加载了outter1 # 注意不运行index()函数,直接执行脚本文件,也会打印上面这3行话的!!!
执行了wrapper1
执行了wrapper2
执行了wrapper3
from index
执行wrapper3 结束了
执行wrapper2 结束了
执行wrapper1 结束了
--------------------------------
.
.
手稿
注意
装饰器语法糖在加载的时候,是从下往上加载的,
但是执行是从最上面的,装饰器的内层函数开始执行的
我们可以看到当最下面的装饰器加载的时候,装饰器函数运行的返回值就是内层函数的函数名,
会再传到上面一层的装饰器函数的括号里,所有装饰器函数都走完了,
最上面的装饰器函数的返回值也就是内层函数的函数名,才会赋值给被装饰的函数的函数名
所以最后,我们运行被装饰的函数的时候,是先运行最上面的装饰器函数的内层函数的!!!!
有参装饰器
# 就是在原来无参装饰器的基础上再套一层自定义函数,
# 这时候就可以在最外层的的装饰器函数的括号里进行传参,不会影响被装饰函数体代码的正常运行。
函数在调用时,校验用户是否登录的装饰器
def outer(mode):
def login_auth(func_name):
def inner(*args, **kwargs):
username = input('username>>>:').strip()
password = input('password>>>:').strip()
if mode == '1':
print('数据直接写死')
elif mode == '2':
print('数据来源于文本文件')
elif mode == '3':
print('数据来源于字典')
elif mode == '4':
print('数据来源于MySQL')
return inner
return login_auth
'''当装饰器中需要额外的参数时>>>:有参装饰器'''
.
这时候函数体运行不了,代码mode没有值!!!
.
在原代码的基础上再包一层,再最外层的定义函数括号里面放一个mode
.
注意这个时候语法糖也变了!!
.
这个时候@outer('1')就等于@login_auth然后就变成index=login_auth(index)
login_auth(index)函数体代码运行完,返回值时innner,所以最终index=inner
所以最终index()实际就是运行的inner()函数体代码!!!!
"""
函数名加括号执行优先级最高 有参装饰器的情况
先看函数名加括号的执行
然后再是语法糖的操作
"""
先运行outer('1')的函数体代码,将返回值login_auth函数名给@接收,
这样就变成了@login_auth ,这样就又变成了无参装饰器的语法结构了,
index=login_auth(index),先运行等号右边的login_auth(index)函数体代码,
将返回值inner函数名返回给变量名index,最后index()就变成了inner(),
运行index()就变成了运行inner函数体代码了!!!
@outer('1')
def index():
print('from index')
index()
@outer('2')
def func():
print('from func')
func()
装饰器模板
# 最常用的无参装饰器
def outer(func_name):
def inner(*args, **kwargs):
res = func_name(*args, **kwargs)
return res
return inner
@outer
def index():
pass
-------------------------------------------------
# 不常用的有参装饰器
def outer_plus(mode):
def outer(func_name):
def inner(*args, **kwargs):
res = func_name(*args, **kwargs)
return res
return inner
return outer
@outer_plus('MySQL')
def func():
pass
-------------------------------------------------
装饰器修复技术
作用就是在装了装饰器后,调用函数体代码运行时,查看函数体代码运行时,
返回值变成了正在的被装饰函数体代码运行后的返回值,欺骗性更高了。
def index():
"""index函数 非常的牛"""
pass
help(index) # help方法会给你打印出 该函数的名字,以及该函数里面的注释!!!
help(len) # 这个就是查看 该len函数或者类的名字,以及该len函数或类里面的注释
-------------------------------------------------------
from functools import wraps # 1 就加两行代码
def outer(func_name):
# 2 这个装饰器一写,打印被装饰函数的函数名,打出的就真的是被装饰函数名了
@wraps(func_name)
def inner(*args, **kwargs):
"""我是inner 我擅长让人蒙蔽"""
res = func_name(*args, **kwargs)
return res
wrapper.__name__ = func.__name__ # 3
# 这行代码的作用是将被装饰的函数的名称赋值给装饰器内部定义的包装函数(wrapper)的名称。
# 这样做的目的是保留被装饰函数的原始名称,这在调试和日志记录等情况下非常有用。
return inner
@outer
def func():
"""我是真正的func 我很强大 我很牛 我很聪明"""
pass
help(func) # 不用@wraps(func_name) 打印的函数名是inner
# 用了 @wraps(func_name) 打印的函数名,就又变成了原函数名了
func() # 还是正常执行装饰器函数的内层函数 inner
# 这三行装饰器代码的目的就是让装饰器,装的更像一点,被装饰的函数运行时,
# 还是执行的装饰器函数的内层函数 inner
装饰器的特殊使用情况
# flask项目的试图函数,我们函数括号里面一般是不写参数的
# 因为请求进flask框架后,匹配到路由对应的试图函数后,就会自动加括号运行
# 所以如果你直接在视图函数定义的时候,在括号里面写参数,框架运行起来后,会报错的!!!
-----------------------------------------------
# 在写flask项目的用户鉴权相关接口的时候,会有这样的需求
# 路由匹配上了以后,再校验请求携带的token是否正确,如果没问题,再运行视图函数
# 由于再进行token校验的时候,实际上已经得到了该用户对象了
# 我们的想法是在装饰器已经查到用户对象后,再后续运行视图函数的时候,直接把用户对象传给
# 视图函数,以方便在视图函数的内部直接用该用户对象了,不需要再通过数据库查一遍了
# 原始的代码
def token_required(func):
def wrapper(*args,**kwargs):
print(111)
res = func(*args,**kwargs)
print(222)
return res
return wrapper
@auth_blueprint.route('/update_password_for_other_user', methods=['POST'])
@token_required
def userinfo():
print('haha')
userinfo()
111
haha
222
--------------------------------
# 改进后代码
def token_required_with_user(func):
def wrapper(*args,**kwargs):
user_obj = {'username': "jack"}
print(111)
res = func(user_obj)
print(222)
return res
return wrapper
# 最上面的装饰器是路由匹配,加括号运行视图函数用的
@auth_blueprint.route('/update_password_for_other_user', methods=['POST'])
@token_required_with_user
def userinfo(user_obj):
print(user_obj)
print('haha')
userinfo()
111
222
{'username': 'jack'}
haha
# 最上面装饰器负责,找到路由对应的函数,然后加括号运行,不会主动给函数传参
# 下面的装饰器通过校验token拿到用户对象,并且顺便在运行视图函数的时候,把用户对象传进去了
# 这样,在视图函数里面,就可以直接拿到该用户对象了!!!
装饰器正常能碰到的最复杂的情况!!!
# 多层语法糖加有参装饰器的情况
def ex_outter1(mode):
print(mode,'迷糊了吧')
def outter1(func1):
print('加载了outter1')
def wrapper1(*args, **kwargs):
print('执行了wrapper1')
res1 = func1(*args, **kwargs) # func1 实际上是wrapper2函数名
print('执行wrapper1 结束了')
return res1
return wrapper1
return outter1
def ex_outter2(mode):
print(mode, '还好')
def outter2(func2):
print('加载了outter2')
def wrapper2(*args, **kwargs):
print('执行了wrapper2')
res2 = func2(*args, **kwargs) # func2 实际上是wrapper3函数名
print('执行wrapper2 结束了')
return res2
return wrapper2
return outter2
def outter3(func3):
print('加载了outter3')
def wrapper3(*args, **kwargs):
print('执行了wrapper3')
res3 = func3(*args, **kwargs) # func3 才是真正的被装饰的函数名
print('执行wrapper3 结束了')
return res3
return wrapper3
@ex_outter1('hello')
@ex_outter2('ni hao')
@outter3
def index():
print('from index')
# 这种情况下,注意函数名加括号的执行优先级最高!!!
# @ex_outter1('hello') 会先执行ex_outter1('hello')函数 最后就变成了 @outter1
# 同理ex_outter2('ni hao')执行完就变成了 @outter2
# 所以最后还是变成了
@outter1
@outter2
@outter3
def index():
print('from index')
# 所以当加载到@outter3时,还是先运行 outter3(index) 然后把函数运行的返回值,返给上面
# 让outter2(wrapper3) 函数能被调用运行,同理运行后的返回值,再返给上面
# 让outter1(wrapper2) 函数能被调用运行,同理运行后的返回值wrapper1
# 由于@outter1已经是最顶层的语法糖了,所以最后将 wrapper1 赋值给index变量名
# 也就是 index=wrapper1
# 也就是还没有运行被装饰的函数index前,上面的代码都已经加载完了,执行结果如下
hello 迷糊了吧
ni hao 还好
加载了outter3
加载了outter2
加载了outter1
# 当运行index()函数后,结果如下
index()
# 还是一样,从最外层的装饰器函数的内层函数wrapper1开始执行!!!
执行了wrapper1
执行了wrapper2
执行了wrapper3
from index
执行wrapper3 结束了
执行wrapper2 结束了
执行wrapper1 结束了
.
.
.
.
.
.
.
.
.
.
.
.
.
递归函数
1.函数的递归调用
函数直接或者间接的调用了函数自身
# 直接调用
# def index():
# print('from index')
# index()
# index()
# 间接
# def index():
# print('from index')
# func()
#
# def func():
# print('from func')
# index()
#
# func()
'''最大递归深度:python解释器添加的安全措施'''
# count = 0
# def index():
# global count
# count += 1
# print(count)
# index()
# index()
'''官网提供的最大递归深度为1000 我们在测试的时候可能会出现996 997 998'''
2.递归函数
1.直接或者间接调用自己
2.每次调用都必须比上一次简单 并且需要有一个明确的结束条件
递推:一层层往下
回溯:基于明确的结果一层层往上
"""
get_age(5) = get_age(4) + 2
get_age(4) = get_age(3) + 2
get_age(3) = get_age(2) + 2
get_age(2) = get_age(1) + 2
get_age(1) = 18
"""
def get_age(n):
if n == 1:
return 18
return get_age(n-1) + 2
res = get_age(5)
print(res)
.
.
.
.
.
.
作业
1.利用递归函数依次打印列表中每一个数据值
l1 = [1,[2,[3,[4,[5,[6,[7,[8,]]]]]]]]
2.整理今日内容及博客
3.利用有参装饰器编写多种用户登录校验策略
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY