python闭包函数与装饰器
- 闭包函数
- 闭包概念
- 实际应用
- 装饰器
- 简介
- 简单版本装饰器
- 进阶版本装饰器
- 完整版本装饰器
- 装饰器模板
- 装饰器语法糖
- 装饰器修复技术
- 问题
- 答案
闭包函数
闭包概念
- 闭:定义在函数内部的函数
- 包:内部函数使用了外层函数名称空间中的名字
# 闭包函数
def outer():
x = 111
# 定义在函数内部的函数
def inner():
# 使用了外层函数名称空间中的名字
print(x)
return inner
x = 666
res = outer()
res() # 输出:111
实际应用
闭包函数是给函数体传参的另外一种方式
def outer(username):
def index():
print(username)
return index
res = outer('kevin') # 形参username与值kevin临时绑定
res() # 输出:kevin
res1 = outer('jason') # 形参username与值jason临时绑定
res1() # 输出:jason
这时候有人就很迷惑了,明明只要定义一个函数index()就可以了,为什么要用outer()包起来传参呢?别急,这是为接下来的内容做铺垫用的。
装饰器
简介
装饰器并不是一个新的知识点,而是由前面所有的函数知识点整合到一起的产物。
装饰器的本质:在不改变被装饰对象原有的“调用方式”和“内部代码”的情况下给被装饰对象添加新的功能。
这是不是听起来有些神奇?装饰器的原则就是对扩展开放,对修改封闭。
简单版本装饰器
了解完什么是装饰器之后,我们先学习使用简单版本的装饰器。
现在有一段代码:
import time # 导入模块,不用管什么意思
def index():
time.sleep(1) # 让程序休眠1秒钟
print('这里是index函数')
def home():
time.sleep(1) # 让程序休眠1秒钟
print('这里是home函数')
我如何能在不动上面2个函数的情况下,添加一个功能:输出函数运行的时长。
这时候有人就要说了:啊?这不是很简单,我只要添加一个函数
def get_time(function_name):
start_time = time.time() # 获取运行函数前的时间
function_name() # 调用函数
end_time = time.time() # 获取运行结束后的时间
print(end_time - start_time) # 输出时间差
get_time(index)
get_time(home)
这个方法确实是可以,但是运行函数都要调用get_time()。
所以别着急,装饰器的功能还没了解全之前,请先继续看下去。
实现:
import time # 导入模块,不用管什么意思
# 装饰对象
def index():
time.sleep(1) # 让程序休眠1秒钟
print('这里是index函数')
def home():
time.sleep(1) # 让程序休眠1秒钟
print('这里是home函数')
# 装饰器
def outer(function_name):
def inner():
# 获取运行函数前的时间
start_time = time.time()
# 调用名为function_name函数
function_name()
# 获取函数运行结束后的时间
end_time = time.time()
# 输出时间差
print(end_time - start_time)
# 返回内部函数名
return inner
# 让index()变成inner()
index = outer(index)
index()
"""
输出:
这里是index函数
1.0101792812347412
"""
# 狸猫换太子
home = outer(home)
home()
"""
输出:
这里是home函数
1.0047881603240967
"""
这个时候函数只需要调用它自己就能输出运行时间了,相当于给函数内部添加了一段代码一样。
进阶版本装饰器
这时候如果函数有参数该怎么做呢?并且参数还不知道几个?
很简单,只要加上可变长形参就行了。
实现:
import time
def index(a): # 有参数
time.sleep(1)
print('这里是index函数', a)
def home(): # 无参数
time.sleep(1)
print('这里是home函数')
def outer(function_name):
def inner(*args, **kwargs): # 添加可变长形参
start_time = time.time()
function_name(*args, **kwargs) # 传参
end_time = time.time()
print(end_time - start_time)
return inner
index = outer(index)
index(555)
"""
输出:
这里是index函数 555
1.0047197341918945
"""
home = outer(home)
home()
"""
输出:
这里是home函数
1.0003490447998047
"""
完整版本装饰器
这个时候问题又出现了,如果函数有返回值又该怎么办?
也很简单,在装饰器内部函数加一个return返回值就行了。
实现:
import time
def index(a):
time.sleep(1)
print('这里是index函数')
return a # 有返回值
def home():
time.sleep(1)
print('这里是home函数')
def outer(function_name):
def inner(*args, **kwargs):
start_time = time.time()
# 将函数返回值赋值给res
res = function_name(*args, **kwargs)
end_time = time.time()
print(end_time - start_time)
"""返回函数的返回值"""
return res
return inner
index = outer(index)
print(index(555))
"""
输出:
这里是index函数
1.0073633193969727
555
"""
home = outer(home)
home()
"""
输出:
这里是home函数
1.0051474571228027
"""
装饰器模板
还没搞明白装饰器怎么回事?小问题!这里有一套万能模板,只要知道怎么使用装饰器就行了。
'''编写装饰器其实有一套固定的代码 不需要做任何理解'''
def outer(func_name): # func_name用于接收被装饰的对象(函数)
def inner(*args, **kwargs):
print('执行被装饰函数之前 可以做的额外操作')
res = func_name(*args, **kwargs) # 执行真正的被装饰函数
print('执行被装饰函数之后 可以做的额外操作')
return res # 返回真正函数的返回值
return inner
装饰器语法糖
由于感觉在这块代码太繁琐了
index = outer(index)
index()
于是python给了一个方法,用@+装饰器名称放在装饰对象的上方。
实现:
def outer(func_name):
def inner(*args, **kwargs):
# 额外操作
res = func_name(*args, **kwargs)
# 额外操作
return res
return inner
# 装饰器语法糖
@outer
def index():
print('这里是index函数')
"""可以直接使用index(),省去了一个步骤"""
index()
语法糖内部原理:
- 使用的时候最好紧跟在被装饰对象的上方
- 语法糖会自动将下面紧挨着的函数名传给@后面的函数调用
装饰器修复技术
在了解装饰器修复技术之前,我们先了解被装饰对象的内部属于谁了
def outer(func_name):
def inner(*args, **kwargs):
res = func_name(*args, **kwargs)
return res
return inner
@outer
def index():
print('这里是index函数')
print(index)
"""
输出:
.inner at 0x0000025BA5B9F268>
"""
可以看出,这时的index已经是inner的人了,这就让人很不爽了,所以我们要把它恢复回来。
from functools import wraps # 导入模块
def outer(func_name):
@wraps(func_name) # 修复
def inner(*args, **kwargs):
res = func_name(*args, **kwargs)
return res
return inner
@outer
def index():
print('这里是index函数')
print(index)
"""
输出:
"""
可以看到,index还是原来的index。
问题
编写一个用户认证装饰器:
-
基本要求:
执行每个函数的时候必须先校验身份,eg: jason 123
-
拔高练习:
执行被装饰的函数,只要有一次认证成功,那么后续的校验都通过
提示:全局变量 记录当前用户是否认证
答案
点击查看代码(这里只展示简易版本)
# 定义全局变量,用于判断用户是否成功登录过一次
is_login = False
# 编辑装饰器
def login(function_name):
def for_login(*args, **kwargs):
global is_login # 声明is_login使用的是全局变量
# 如果is_login为True就不需要登录了
if is_login:
res = function_name(*args, **kwargs)
return res
# 用户没有登录过的情况
print("你还没有登录哦!")
username = input("请输入用户名:").strip()
password = input("请输入密码:").strip()
if username == 'yume' and password == '123':
print("登录成功!")
is_login = True
res = function_name(*args, **kwargs)
return res
else:
print("用户名或密码错误!")
return for_login
@login
def print_any():
print("输入什么就输出什么")
s = input()
print(s)
@login
def add():
print("将2个字符串相加")
a = input("输入第一个:")
b = input("输入第二个:")
print(a + b)
print("""
1.输入什么就输出什么
2.将2个字符串相加
""")
while True:
choice = input("请输入指令:")
if choice == '1':
print_any()
elif choice == '2':
add()
else:
print("错误指令")