函数篇:多层及有参装饰器、递归函数、算法之二分法
2022.3.21学习笔记
- 多层语法糖
- 有参装饰器
- 递归函数
- 算法之二分法
一、多层语法糖(装饰器)
首先,我们需要先定义多个装饰器
def outer1 (func1):
print('执行了outer1')
def inner1(*args, **kwargs):
print('执行了inner1')
res1 = func1(*args, **kwargs)
return res1
return inner1
def outer2 (func2):
print('执行了outer2')
def inner2(*args, **kwargs):
print('执行了inner2')
res2 = func2(*args, **kwargs)
return res2
return inner2
def outer3 (func3):
print('执行了outer3')
def inner3(*args, **kwargs):
print('执行了inner3')
res3 = func3(*args, **kwargs)
return res3
return inner3
# 再定义一个函数
@outer1 # inner2 = outer1(inner2)
@outer2 # inner3 = outer2(inner3)
@outer3 # index = outer3(index)
def index():
print('执行了index')
观察这个多层装饰器,结果会如何运行呢?
其实,上面装饰器的外层函数是自下而上进行调用的,而到了最上层装饰器之后,下面调用index时,再自上而下运行内部函数
二、有参装饰器
首先有参装饰器顾名思义就是有参数的装饰器,如:
@outer(a)
思考:这个a参数如何传入装饰器内部使用呢?
不妨将outer装饰器写出来,如下:
def outer (func):
def inner1(*args, **kwargs):
print(A) # 假设装饰器内需要a参数
res = func(*args, **kwargs)
return res
return inner
可以发现,闭包函数内外两层函数的参数已经写死了,不能再添加参数了,那么我们如何从外部传入参数呢,只有再包一层函数了:
def data(A):
def outer (func):
def inner1(*args, **kwargs):
print(A)
res = func(*args, **kwargs)
return res
return inner
return outer
@data(a) # 先不看@符号,data(a)的返回值为outer,因此@data(a) == @outer,即index = outer(index)
def index():
...
这样的话采用函数多层嵌套的方式成功传入参数,也没有改变装饰器的本质,大功告成!
这里需要注意一个知识,函数执行的优先级是最高的,因此需要先看@符号右边的结果是什么
三、递归函数
特征:在函数运行过程中直接或者间接调用了自身的函数称之为递归函数,ru:
1.直接调用
def index():
print('666')
index()
2.间接调用
def index():
print('111')
func()
def func():
print('222')
index()
# 结果报错,超出最大递归深度
这样调用的结果就是函数无限执行下去直到达到解释器的限制,最大递归限制
最大递归限制(深度):官网给出的是1000,回答997,998等上下都可以,而且最大递归限制可以更改,如下:
import sys
print(sys.getrecursionlimit()) # 获取默认的最大递归深度1000
sys.setrecursionlimit(2000) # 还可以修改最大递归深度
那么我们仍然会发现,这样的函数运行是毫无意义的,因为永远没有结果,因此我们需要加上一些人为的条件,让函数运行达到一定条件后,结束运行,并得到一个结果。
需求:
如果教室里面前面一个学生的年龄比后面一个学生年龄大两岁,最后一排的学生年龄为18岁,那么,第一排年龄是多少?
# 定义一个函数进行计算
def get_age(num):
if num == 1:
return 18 # 结束条件
return ger_age(num-1) + 2 # 计算公式,后面比前面小2岁,因此需要+2
print(get_age(5))
# get_age(5) = get_age(4) + 2
# get_age(4) = get_age(3) + 2
# get_age(3) = get_age(2) + 2
# det_age(2) = get_age(1) + 2
像这样一直倒推到num为1的时候,我们知道get_age = 18,再把之前的结果都相加,就能得到结果。
像这样在最初的函数输入有几排学生就能得到第一排学生的年龄
这就是递归函数,可以发现他的特征有:
1、每次递归,复杂度都会降低,相当于每次递归都与答案越来越近
2、必须要有明确的结束条件
拓展:
isinstance(i, int) # 判断i是否为右边整型
四、算法之二分法
二分法是算法里面最入门的一个,主要为了感受算法的魅力
使用前提:数据集必须有升序或者降序
l = [13,14,35,46,58,67,77,86,92,123,245,365,421,578,654,768]
# 问题:查找一个数123
常规操作:for循环列表
优点:无需注重顺序
缺点:效率不高
那么如果使用二分法呢,先定义一个函数:
def get_num(l1,target_num): # 定义函数,先不写内容
...
get_num(l, 123) # 调用函数
大概形式是这样,然后需要思考,怎么拿到最中间的数,然后计算呢
def get_num(l1,target_num):
if len(l1): # 即列表l1长度不为0时
mid_num = len(l1) // 2 # 列表长度整除2
if l1[mid_num] > target_num: # 判断目标数字在中间数的左边或者右边
l1_left = l1[:mid_num]
print(l1_left)
get_num(l1_left, target_num) # 得到一半列表,继续递归
elif l1[mid_num] < target_num:
l1_right = l1[mid_num:]
print(l1_right)
get_num(l1_right, target_num)
else:
print('找到了', target_num)
get_num(l, 123)
这样,利用二分法就找到了123这个数字,但是二分法也有缺陷
优点:效率高,不需要一个一个找
缺点:如果需要的数据在头尾,没有for循环简单
后面还需要了解一些算法,比如快排、插入、冒泡、堆排等