今日主要内容
- 装饰器扩展
- 有参装饰器
- 多个装饰器装饰一个函数
- 递归
一、装饰器扩展
(一)含有参数的装饰器
先来回顾一下装饰器的标准模式
def wrapper(fn): def inner(*args, **kwargs): """扩展内容""" ret = fn(*args, **kwargs) """扩展内容""" return inner @wrapper def func(): pass func()
回顾一下之前的游戏模拟,通过装饰器给我的游戏过程扩展了开挂的功能,装饰之后每次想玩游戏的时候调用函数都会给你先把挂打开,此时你的游戏函数已经被装饰了,但是现在有一个问题,我今天想自己玩一把,不想开挂了,怎么办?我们可以给装饰器传一个参数,来控制我的装饰器的开启和关闭就可以了
def wrapper_outer(argv): # 给装饰器加一个参数,控制装饰器开启关闭 def wrapper(fn): def inner(hero): if argv: # 如果是True执行添加装饰 print("开启外挂!") ret = fn(hero) print("关闭外挂!") return ret else: # 如果是False,执行原函数 ret = fn(hero) return ret return inner return wrapper @wrapper_outer(True) def play_lol(hero): # 基础函数参数 print("登陆游戏") print("开始排位...") print(f"选择英雄:{hero}") print("游戏中...") print("胜利!!!") print("结束游戏") return "坑比队友:xxx" # 基础函数返回值 print(play_lol("盖伦")) 运行结果: 开启外挂! 登陆游戏 开始排位... 选择英雄:盖伦 游戏中... 胜利!!! 结束游戏 关闭外挂! 坑比队友:xxx
刨析一下:
- 先来看装饰器和语法糖
@wrapper_outer(True)
先执行函数调用,函数调用返回的是我内层装饰器的函数名,相当于@wrapper
- 装饰器最外层的参数控制内层包装函数inner里面的函数部分是否执行,如果argv为真,相当于执行了包装,如果argv为假,执行原函数
- 通过给装饰器传参起到了控制装饰器是否生效的功能
def wrapper_outer(argv): def wrapper(fn): def inner(hero): if argv: # 为真执行这里 print("开启外挂!") ret = fn(hero) print("关闭外挂!") return ret else: # 为假执行这里 ret = fn(hero) return ret return inner return wrapper @wrapper_outer(True) # 先执行函数调用
- 注意:一旦给函数装饰过,装饰器的参数是不能变化的,因为闭包的原因参数已经被闭进去了,只能调用内层函数,无法再修改最外层的装饰器参数
flag = True def wrapper_outer(argv): def wrapper(fn): def inner(*args, **kwargs): if argv: """扩展功能""" ret = fn(*args, **kwargs) """扩展功能""" return ret else: ret = fn(*args, **kwargs) return ret return inner return wrapper @wrapper_outer(flag) def func(): pass flag = False func() # 此时flag依然是True,装饰过就不能修改参数的值
- 先来看装饰器和语法糖
有参装饰器的标准模式
def wrapper_outer(argv): def wrapper(fn): def inner(*args, **kwargs): if argv: """扩展功能""" ret = fn(*args, **kwargs) """扩展功能""" return ret else: ret = fn(*args, **kwargs) return ret return inner return wrapper @wrapper_outer(True) def func(): pass func()
(二)多个装饰器装饰一个函数
执行原理:从里到外进行包装
def wrapper1(fn): def inner(*args, **kwargs): print("扩展功能1") ret = fn(*args, **kwargs) print("扩展功能4") return ret return inner def wrapper2(fn): def inner(*args, **kwargs): print("扩展功能2") ret = fn(*args, **kwargs) print("扩展功能3") return ret return inner @wrapper1 @wrapper2 def func(): print("目标函数") func() 运行结果: 扩展功能1 扩展功能2 目标函数 扩展功能3 扩展功能4
刨析一下:
- 从里往外看,先用第一层装饰器
@wrapper2
装饰目标函数func()
,装饰完将其看作成一个整体,在被上层装饰器@wrapper1
装饰 - 返回值:执行完目标函数,将目标函数的返回值先反给最近的装饰器
@wrapper2
内部的inner包装函数中,之后再将@wrapper2
内部的inner包装函数的返回值返回给上一层装饰器@wrapper1
内部的inner中,最终得到的返回值是我调用函数的返回值 - 最终调用目标函数其实真正执行的是最外层装饰器中的包装函数
inner
,而最外层装饰器中的包装函数inner
包装着内层装饰器的包装函数inner
,而内层装饰器的包装函数inner
包装着真正的目表函数func
# 伪代码: def 装饰器1(传入目标函数): def 内层包装函数1,也是真正执行的函数(目标函数的参数): """前扩展功能""" 目标函数(目标函数的参数) """后扩展功能""" return 包装函数的函数名 def 装饰器2(传入目标函数): def 内层包装函数2,也是真正执行的函数(目标函数的参数): """前扩展功能""" 目标函数(目标函数的参数) """后扩展功能""" return 包装函数的函数名 @装饰器1 @装饰器2 def 目标函数(形参): 函数体 目标函数(实参) # 真正执行过程: 先执行:装饰器1的内层包装函数1,而传入的目标函数是:装饰器2的内层包装函数2 再执行:装饰器2的内层包装函数2,而传入的目标函数是:目标函数
- 从里往外看,先用第一层装饰器
二、递归
(一)什么是递归
- 首先递归是一个函数,只要满足两个要求的函数就是递归函数:
- 不断调用自己本身
- 有明确的结束条件
(二)递归深度
如果只是在不断的调用自己本身,没有一个明确的结束条件,那么就是一个死递归(无限循环)。
Python官方规定,为了避免无限制的调用自己本身,递归的最大深度为1000(最多只能调用自己本身1000次),实际递归深度为998
def func(): print(1) func() func() 运行结果: [Previous line repeated 994 more times] 1 1 1 ...(共打印998个1)
可以通过导入sys模块,修改最大递归深度
import sys sys.setrecursionlimit(100) # 修改递归深度 def func(): print(1) func() func() 运行结果: [Previous line repeated 94 more times] 1 1 1 ...(实际打印98个1)
(三)递归的应用
求n的阶乘
def factorial(n): if n == 1: return 1 return factorial(n - 1) * n print(factorial(5)) 运行结果: 120
计算斐波那契序列
def fib(n): if n <= 2: return 1 return fib(n-1) + fib(n-2) print(list(map(fib,range(1, 6)))) 运行结果: [1, 1, 2, 3, 5]
打印列表嵌套的每一个元素
l1 = [1, 2, [3, 4, [5, [6, 7, [8, 9], 10], 11, 12], 13], 14, 15] def func(lst): for el in lst: if type(el) == list: func(el) else: print(el, end=" ") func(l1) 运行结果: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
给列表去重,不能使用集合
l1 = [1, 1, 2, 3, 4, 5, 6, 3, 3, 5, 6, 3, 4, 5] def del_repetition(lst): for el in lst: if lst.count(el) > 1: lst.remove(el) del_repetition(lst) del_repetition(l1) print(l1) 运行结果: [1, 2, 6, 3, 4, 5]
遍历文件夹中所有的文件
import os def read(filepath, n): files = os.listdir(filepath) # 获取到当前文件夹中的所有文件 for fi in files: # 遍历文件夹中的文件, 这里获取的只是本层文件名 fi_d = os.path.join(filepath, fi) # 加入文件夹 获取到文件夹文件 if os.path.isdir(fi_d): # 如果该路径下的文件是文件夹 print("\t" * n, fi) read(fi_d, n + 1) # 继续进行相同的操作 else: print("\t" * n, fi) # 递归出口. 最终在这里隐含着return # 递归遍历目录下所有文件 read('../day16/', 0)
二分查找
# 普通递归版本⼆二分法 lst = [22, 33, 44, 55, 66, 77, 88, 99, 101, 238, 345, 456, 567, 678, 789] n = 567 left = 0 right = len(lst) - 1 def binary_search(n, left, right): if left <= right: middle = (left + right) // 2 if n < lst[middle]: right = middle - 1 elif n > lst[middle]: left = middle + 1 else: return middle return binary_search(n, left, right) # 这个return必须要加. 否则接收到的永远是None. else: return -1 print(binary_search(567, 0, len(lst) - 1))
三级菜单进入返回
menu = { '北京': { '海淀': { '五道口': { 'soho': {}, '网易': {}, 'google': {} }, '中关村': { '爱奇艺': {}, '汽车之家': {}, 'youku': {}, }, '上地': { '百度': {}, }, }, '昌平': { '沙河': { '北邮': {}, '北航': {}, }, '天通苑': {}, '回龙观': {}, }, '朝阳': {}, '东城': {}, }, '上海': { '闵行': { "人民广场": { '炸鸡店': {} } }, '闸北': { '火车战': { '携程': {} } }, '浦东': {}, }, '天津': { "和平": { "小白楼": {}, "五大道小洋楼": {}, "滨江道": {}, }, "南开": { "天大": {}, "南开": {}, "理工": {}, }, "河北": { "天津之眼": {}, "海河": {}, "意式风情区": {}, "世纪钟": {}, "大悲院": {}, }, }, } def menu_func(menu): while True: for k in menu: print(k) key = input('input>>').strip() if key == 'b' or key == 'q': return key elif menu.get(key): ret = menu_func(menu[key]) if ret == 'q': return 'q' menu_func(menu)