闭包函数与装饰器
闭包函数
什么是闭函数?
闭就是封闭的意思,函数被封闭起来,f2就是闭函数,f2只能在f1的局部访问
def f1():
def f2():
pass
什么是包函数?
函数内部包含对外层函数作用域名字的引用,不包含全局
def f1():
x = 10
def f2():
print(x)
f2被封闭在f1内部了,要调用它只能在f1内部调用,全局是没办法访问到f2的。
需求:全局访问f2
解决思路:函数可传递、可以当做返回值被另一个函数返回,把局部函数的内存地址拿到全局来用
def f1():
x = 10
def f2():
print(x)
return f2
# res 为f2的内存地址
res = f1()
print(res)
x = 20
res() # 输出结果为10
在全局修改x=20,f1打印的还是10,因为名字的查找顺序是在定义阶段就决定的,和我们在哪里调用时没有关系的,找x时,找的是包里的x。要做到x可修改,将x作为f1的入参。
def f1(x):
def f2():
print(x)
return f2
# res 为f2的内存地址
res = f1(10)
res()
什么时候用到闭包函数?
f2不能够增加新的形参,而f2又需要外部传参
应用场景:装饰器
装饰器
器:工具,造工具使用函数就可以
装饰:为其他事物添加额外功能
装饰器:定义一个函数/类,这个函数/类的功能就是用来给其他函数添加额外功能的
给函数增加功能还需要单独写一个函数吗?
开放封闭原则
面向对象的核心
开发:对拓展功能(增加功能)开放,在源代码不做任何修改的情况下,为其增加新功能
封闭:对修改源代码是封闭的
增加新的功能,增加新的功能,增加新的功能
不要去动源代码,写一个新函数给它增加新功能
示例
eg:
import time
def inside(group, s):
print('欢迎来到英雄联盟')
print(f'你出生在{group}方阵营')
print(f'敌军还有{s}秒到达战场')
time.sleep(s)
print('全军出击')
inside('蓝色', 3)
在此函数基础上增加统计该函数执行时间
方案一:没有修改调用方式,修改了源代码,违反开放封闭原则
import time
def inside(group, s):
start = time.time()
print('欢迎来到英雄联盟')
print(f'你出生在{group}方阵营')
print(f'敌军还有{s}秒到达战场')
time.sleep(s)
print('全军出击')
end = time.time()
print(end-start)
inside('蓝色', 3)
方案二:没有修改源码和调用方式,但加入inside多次调用,存在大量重复代码,low
import time
def inside(group, s):
print('欢迎来到英雄联盟')
print(f'你出生在{group}方阵营')
print(f'敌军还有{s}秒到达战场')
time.sleep(s)
print('全军出击')
start = time.time()
inside('蓝色', 3)
end = time.time()
print(end - start)
start = time.time()
inside('蓝色', 3)
end = time.time()
print(end - start)
start = time.time()
inside('蓝色', 3)
end = time.time()
print(end - start)
方案三:解决方案二代码冗余问题,没有修改源代码,增加了新功能,但是改变了调用方式,而且wrapper函数inside调用传参写死了
import time
def inside(group, s):
print('欢迎来到英雄联盟')
print(f'你出生在{group}方阵营')
print(f'敌军还有{s}秒到达战场')
time.sleep(s)
print('全军出击')
def wrapper():
start = time.time()
inside('蓝色', 3)
end = time.time()
print(end - start)
wrapper()
方案四:为了解决参数写死问题,使用*args **kwargs
解决
import time
def inside(group, s):
print('欢迎来到英雄联盟')
print(f'你出生在{group}方阵营')
print(f'敌军还有{s}秒到达战场')
time.sleep(s)
print('全军出击')
def wrapper(*args, **kwargs):
start = time.time()
inside(*args, **kwargs)
end = time.time()
print(end - start)
wrapper('蓝色', 3)
方案五:假如另外一个函数也需要统计执行时间,wrapper没法直接复用
传参两种方式
直接传:因为传入的参数时直接给被修饰对象的,不能直接传
闭包函数:将修饰的地址返回重新复制,对于调用者来说不感知
import time
def inside(group, s):
print('欢迎来到英雄联盟')
print(f'你出生在{group}方阵营')
print(f'敌军还有{s}秒到达战场')
time.sleep(s)
print('全军出击')
def outer(func):
# func = inside
def wrapper(*args, **kwargs):
start = time.time()
func(*args, **kwargs)
end = time.time()
print(end - start)
return wrapper
# func 指向 inside
# inside 指向 outer(inside)
inside = outer(inside)
# 对于调用者来说,不知道被修饰了
inside('蓝色', 3)
方案六 加入原函数有返回值,使用上面的装饰器,返回值会丢失
import time
def recharge(num):
for i in range(num, 101):
time.sleep(0.05)
print(f'\r当前电量:{"*"*i} {i}%', end='')
print('充电完毕!')
return 100
def outer(func):
# func = inside
def wrapper(*args, **kwargs):
start = time.time()
response = func(*args, **kwargs)
end = time.time()
print(end - start)
return response #增加返回值
return wrapper
recharge = outer(recharge)
res = recharge(20)
print(res)
语法糖
用了这个语法之后,代码看起来更加简洁,写起来很爽,看起来也很爽,就像吃了糖一样。
每次装饰对象,都需要执行以下outer,把被装饰对象传进去,得到新的函数地址,再覆盖原来的名字。
有没有一种简洁的方式来完成这件事?
在需要装饰对象前增加@装饰名字
import time
def count_time(func):
# func = inside
def wrapper(*args, **kwargs):
start = time.time()
response = func(*args, **kwargs)
end = time.time()
print(end - start)
return response
return wrapper
@count_time # inside = count_time(inside)
def inside(group, s):
print('欢迎来到英雄联盟')
print(f'你出生在{group}方阵营')
print(f'敌军还有{s}秒到达战场')
time.sleep(s)
print('全军出击')
@count_time # recharge = count_time(recharge)
def recharge(num):
for i in range(num, 101):
time.sleep(0.05)
print(f'\r当前电量:{"*"*i} {i}%', end='')
print('充电完毕!')
return 100
recharge(20)
inside('蓝色', 3)
装饰器模板
在pycharm中设置
File->Settings->Live Templates-> Python-> +
def outer(func):
def wrapper(*args, **kwargs):
res = func(*args, **kwargs)
return res
return wrapper
比如用装饰器实现了认证功能,那么 需要认证功能时,只需要使用装饰器即可
def outer(func):
def wrapper(*args, **kwargs):
name = input('请输入账号>>>').strip()
pwd = input('请输入密码>>>').strip()
if name == 'jack' and pwd == '123':
res = func(*args, **kwargs)
return res
else:
print('账号或密码错误!')
return wrapper
@outer
def home():
time.sleep(2)
print('welcome')
home()
完美伪装
之前的装饰器其实还不够完美,因为函数地址、__name__
等属性能看出是否被装饰.
怎么才能伪装的更像?
使用functools中wraps装饰器来装饰wrapper
其实最重要的伪装,参数,调用方式,返回值已经伪装的很好了,wraps只是做一些不太重要的伪装。
from functools import wraps
def outer(func):
@wraps(func)
def wrapper(*args, **kwargs):
name = input('请输入账号>>>').strip()
pwd = input('请输入密码>>>').strip()
if name == 'jack' and pwd == '123':
res = func(*args, **kwargs)
return res
else:
print('账号或密码错误!')
return wrapper
@outer
def home():
time.sleep(2)
print('welcome')
# home()
print(home.__name__)
print(home.__doc__)
有参装饰器
当函数内部需要一个参数时,有两种方案可以实现:
1直接通过参数传入
2用闭包函数概念包给它
如果第一种方案可以解决,就尽量不要用第二种方案
加入wrpper需要一个新参数,怎么传入?
直接在通过outer传入,不行。因为语法糖的限制,只能传一个参数,而且是函数的地址
def outer(func, name): # 报错
def wrapper(*args, **kwargs):
print(name)
res = func(*args, **kwargs)
return res
return wrapper
@outer
def home():
time.sleep(2)
print('welcome')
因此outer参数不能用动,wrapper参数不能动,使用有参装饰器。
def g_outer(name):
def outer(func):
def wrapper(*args, **kwargs):
print(name)
res = func(*args, **kwargs)
return res
return wrapper
return outer
# outer = g_outer('张大仙')
# g_outer()调用返回outer地址,再和@结合构成语法糖
@g_outer('张大仙')
def home():
time.sleep(2)
print('welcome')
装饰器叠加
def outer1(func1):
def wrapper(*args, **kwargs):
print('开始执行outer1.wrapper')
res = func1(*args, **kwargs) # func1 = home
print('outer1.wrapper执行完毕')
return res
return wrapper
def outer2(x):
def outer(func2):
def wrapper(*args, **kwargs):
print('开始执行outer2.wrapper')
res = func2(*args, **kwargs) # func2 = outer1.wrapper
print('outer2.wrapper执行完毕')
return res
return wrapper
return outer
def outer3(func3):
def wrapper(*args, **kwargs):
print('开始执行outer3.wrapper')
res = func3(*args, **kwargs) # func3 = outer2.wrapper
print('outer3.wrapper执行完毕')
return res
return wrapper
@outer3 # home=outer3(home) outer3.wraper
@outer2(10) # home=outer2(home) outer2.wraper
@outer1 # home=outer1(home) outer1.wraper
def home(z):
print('执行home功能', z)
home(0)
执行结果如下
开始执行outer3.wrapper
开始执行outer2.wrapper
开始执行outer1.wrapper
执行home功能 0
outer1.wrapper执行完毕
outer2.wrapper执行完毕
outer3.wrapper执行完毕