day14 装饰器进阶和递归
今日内容概要
- 有参装饰器
- 多个装饰器装饰一个函数(了解)
- 递归(人理解函数,神理解递归)
昨日内容回顾
-
闭包
- 闭包的定义:
- 在嵌套函数内,使用非全局变量(且非本层变量)
- 将嵌套函数返回
- 闭包的作用:
- 保护数据的干净和安全
- 闭包的应用场景:
- 装饰器
- 闭包的定义:
-
装饰器
-
开放封闭原则:
- 对扩展开放,对增加新功能开放
- 对修改源代码封闭,对调用方式封闭
-
标准版装饰器
def func(a): def inner(*args, **kwargs): """装饰前执行""" print('1111') ret = a(*args, **kwargs) """装饰后执行""" print('2222') return ret return inner @func def index(a, b, c): print('被装饰的函数', a, b, c) return '难啊' print(index(1, 2, 3))
-
今日内容详细
有参装饰器
昨天我们探讨了标准版的装饰器。我们知道,装饰器也是函数,就可以接受参数。如果有一天,我们想要给装饰器传递参数该怎么办呢?
或许我们可以在装饰器的外层再套一层函数 用来传参,就像这样:
def decor(arg):
def wrapper(func):
def inner(*args, **kwargs):
if arg:
print('开始装饰')
ret = func(*args, **kwargs)
if arg:
print('装饰结束')
return ret
return inner
return wrapper
def index():
print('is index')
如果还想以往那样直接在index
函数的上面加语法糖@decor
,一来没有达到传入参数的目的,二来也会因为参数传入混乱而报错。
不过我们可以从昨天不用语法糖的方法配置装饰器的操作中获得启发,或许可以通过在下面加入这样几行代码来构建一个可以传入参数的装饰器:
wrapper = decor(True)
index = wrapper(index)
index()
输出的结果为:
开始装饰
is index
装饰结束
其实我们已经成功构成了一个可以传入参数的装饰器。
现在,我们需要的,是把语法糖运用起来,让代码更加简明直观。
我们知道,语法糖@wrapper
相当于index = wrapper(index)
这条语句。而此时wrapper
等同于decor(True)
。没错,对于带参数的装饰器而言,我们可以这样写语法糖:
def decor(arg):
def wrapper(func):
def inner(*args, **kwargs):
if arg:
print('开始装饰')
ret = func(*args, **kwargs)
if arg:
print('装饰结束')
return ret
return inner
return wrapper
@decor(True)
def index():
print('is index')
index()
这个装饰器中参数的作用是,当传入的值为True
,装饰器会被执行;当值为False
,装饰器不会被执行。
我们还可以通过使用带参数的装饰器,实现使用一个装饰器针对不同函数做出不同响应的效果。
例如,让用户选择要登陆的软件,然后针对不同软件使用各自的账号密码:
msg = """
1. 微信
2. 抖音
3. 邮箱
请选择您要登陆软件的编号:"""
choose = input(msg)
def auth(arg):
def wrapper(func):
def inner(*args, **kwargs):
user = input('用户名:')
pwd = input('密码:')
if arg == '1':
if user == 'alex' and pwd == 'alex1234':
func(*args, **kwargs)
else:
print('账号或密码错误!')
elif arg == '2':
if user == 'wusir' and pwd == '1234':
func(*args, **kwargs)
else:
print('账号或密码错误!')
elif arg == '3':
if user == 'meet' and pwd == '1234':
func(*args, **kwargs)
else:
print('账号或密码错误!')
return inner
return wrapper
@auth(choose)
def dy():
print('抖音')
@auth(choose)
def email():
print('邮箱')
@auth(choose)
def wechat():
print('微信')
func_dic = {
'1': wechat,
'2': dy,
'3': email,
}
if choose in func_dic:
func_dic[choose]()
事实上,我们上面的代码在装饰器中有很多重复代码,可以将它们封装到一个函数中。
在函数调用时,如果我们日后学到反射
,会简洁很多。
带参数的装饰器的应用场景为:flask框架的路由就是有参装饰器。
多个装饰器装饰一个函数
一个装饰器可以装饰多个函数,同样地,多个装饰器也可以装饰同一个函数。当多个装饰器装饰同一个函数时,先执行离被装饰函数最近的装饰器:
def f1(func):
def f2(*args, **kwargs):
print('这是f1装饰器开始')
func(*args, **kwargs)
print('这是f1装饰器结束')
return f2
def foo1(func):
def foo2(*args, **kwargs):
print('这是foo1装饰器开始')
func(*args, **kwargs)
print('这是foo1装饰器结束')
return foo2
@foo1 # index = foo1(index) --> foo1(f2)
@f1 # index = f1(index) <== f2
def index():
print('is index')
index()
返回的结果为:
这是foo1装饰器开始
这是f1装饰器开始
is index
这是f1装饰器结束
这是foo1装饰器结束
程序的执行还是从上到下,只要按照流程一步一步走,还是能理清思路的。
对于套更多层的装饰器,规则也是相同的:
def f1(func): # func == index
def f2(*args,**kwargs):
print("sss")
func(*args,**kwargs)
print("stop")
return f2
def foo1(func): # func == f2
def foo2(*args,**kwargs):
print("good")
func(*args,**kwargs)
print("bbb")
return foo2
def ff(func):
def ff2():
print("is ff")
func()
print("ff is")
return ff2
@foo1
@ff
@f1
def f():
print("is f")
f()
返回的结果为:
good
is ff
sss
is f
stop
ff is
bbb
我们其实可以发现这样一个规律:装饰器的执行是被装饰代码在最中间,外层装饰器的运行结果包裹着内层装饰器的运行结果。大体上呈现一个U
字形:
对于多个装饰器装饰一个函数的情况,只需要记住一个原则:先执行离被装饰函数最近的装饰器。
递归
递归的精华是一递一归。所谓递,就是不断嵌套函数;所谓归,就是逐个将值返回。递而不归,就会越嵌套越深,直至突破内存极限而出错。
递归函数的定义有两个方面:
- 不断调用自己本身 # 只满足这个条件的是死递归
- 有明确的结束条件
例如,下面的这个函数就是一个死敌归:
def func():
print(1)
func()
func()
程序并没有一直运行,输出1,而是运行到一定深度(层次)后就停止了。
这是因为Python为了保护计算机而设置了递归的深度限制。官方声明的限制是1000曾,但实际测试往往在998/997层左右。
我们也可以修改系统设置的迭代深度限制:
import sys
sys.setrecursionlimit(800)
现在,让我们用递归写一个阶乘的函数:
def factorial(n):
if n == 1:
return 1
else:
return factorial(n - 1) * n
print(factorial(5))
返回的结果为:120
递归的思路是:找到f(n)
与f(n - 1)
之间的关系,然后将这种关系作为返回值或者其他操作。然后通过设置起始位置或终止位置的函数值实现函数的结束条件。
对于上个例子来说,factorial(n)
与factorial(n - 1)
之间的关系是factorial(n) = factorial(n - 1) * n
。而当n
为1
时,factorial(1) = 1
。
把上面的例子拆开看就是下面这个样子:
函数层数 | n | 返回值 |
---|---|---|
factorial(5) | 5 | factorial(4) * 5 |
1 | 4 | factorial(3) * 4 * 5 |
2 | 3 | factorial(2) * 3 * 4 * 5 |
3 | 2 | factorial(1) * 2 * 3 * 4 * 5 |
4 | 1 | 1 * 2 * 3 * 4 * 5 |
有些问题使用递归解起来会有很奇妙的感觉,比如解决汉诺塔问题等。
但是因为层层嵌套,层层调用,递归非常占用内存,运行速度也相对缓慢。使用尾递归可以略微缓解这个麻烦,但也是慢。