1. 装饰器
1.1 装饰器简介
装饰器的作用 : 对函数进行装饰 , 添加新的功能 .
装饰器的原则 : 开发封闭原则 , 对扩展开放 , 对修改封闭 .
封闭 : 已现实的功能代码块不应该被修改 .
开放 : 对现有功能的拓展开放 .
在不改变被装饰对象 '内部代码' 以及 '调用方式' 的基础上为函数添加新的功能 .
1.2 演变示例
import time 导入时间模块
time . sleep ( 阻塞的秒数 )
time . time ( ) 获取时间戳
计算机元年 : 1970 年 1 月 1 日 0 时 0 分 0 秒到现在的秒数 .
时间戳 : 距离计算机元年的秒数 .
1. 主体函数
设计一个程序完成下列要求 .
* 1. 设计一个函数
* 2. 计算运行函数花费的时间 .
* 1. 设计一个函数 .
import time
def func1 ( ) :
print ( '程序开始执行···' )
time. sleep( 1 )
print ( '程序执行结束···' )
* 2. 为函数添加一个记录执行时间的功能 .
import time
def func1 ( ) :
print ( '程序开始执行···' )
time. sleep( 1 )
print ( '程序执行结束···' )
start_time = time. time( )
func1( )
end_time = time. time( )
print ( '程序执行时间为:%s' % ( end_time - start_time) )
运行工具窗口显示 :
程序开始执行···
程序执行结束···
程序执行时间为 : 1.0120244026184082
2. 演变1
上例只有一个函数 , 在函数中执行前后添加获取时间戳在计算即可 , 那么如果有 100 个函数呢?
按例使用的方法推动需再重复 99 次 , 二次 99 次的代码还是一样的 , 重复执行的代码可以做成一个函数 !
将获取获取时间戳的代码写成一个函数 , 在需要的时候调用 .
* 1. 在需要要重复使用的代码代码块缩进 , 使用 def定义成一个函数 .
import time
def func1 ( ) :
print ( '程序开始执行···' )
time. sleep( 1 )
print ( '程序执行结束···' )
def get_time ( ) :
start_time = time. time( )
func1( )
end_time = time. time( )
print ( '程序执行时间为:%s' % ( end_time - start_time) )
* 2. 这个函数中每次更换执行的函数 , 将这个函数做成一个参数 .
* 3. 调用获取执行时间函数的时候 , 将需要监测的函数名作为实参 , 传递即可 .
设计核心 : 将主函数名作为参数传入另一个函数中 , 在另一个函数中执行主函数 .
设计缺点 : 通过其它函数去调用主函数 , 修改了函数的调用方式 .
3. 演变2
闭包函数是给函数传值的一种方式 , 使用闭包函数来传参 , 这个样就解决了函数调用方式问题 .
def func1 ( ) :
print ( '程序开始执行1···' )
time. sleep( 1 )
print ( '程序执行结束2···' )
def time_func ( func) :
def get_time ( ) :
start_time = time. time( )
func( )
end_time = time. time( )
print ( '程序执行时间为:%s' % ( end_time - start_time) )
return get_time
func1 = time_func( func1)
func1( )
偷梁换柱的概念 : 执行time_func ( ) 时将主函数名作为实参绑定给func , 直接返回get_time .
使用一个主函数的名称接收get_time , 再执行func1 , 其实执行的是get_time .
设计核心 : 使用闭包函数传参 , 主函数在另一个函数中执行主函数 , 但加以掩饰 , 让人感觉调用方式没变 .
设计缺点 : 无法为主函数传递参数 : 为func1 ( 其实是get_time ) 提供参数 , 主函数需要参数拿不到 .
4. 演变3
主函数查找参数的顺序 : 主函数没有去get_time函数中找 , 现在是通过func1调用get_time ( ) ,
那么调用func1时提供参数即可 .
* args 与 * * kwargs组合可以接受任何参数 , 也可以满足不传参数的函数 .
主函数需要参数 -- > get_time函数 -- > func1函数提供参数 .
import time
def func1 ( num) :
print ( '程序开始执行1···' )
time. sleep( 1 )
print ( num)
print ( '程序执行结束2···' )
def time_func ( func) :
def get_time ( * args, ** kwargs) :
start_time = time. time( )
func( * args, ** kwargs)
end_time = time. time( )
print ( '程序执行时间为:%s' % ( end_time - start_time) )
return get_time
func1 = time_func( func1)
func1( 1 )
设计核心 : 闭包函数的参数是 * args 与 * * kwargs组合 , 可以接受任何参数 , 也可以满足不传参数的函数 .
设计缺点 : 无法获取主函数的返回值 .
5. 演变4
获取主函数执行后的返回值 :
主函数中设置返回值 , 执行func ( 主函数 ) 时接收参数 , 这个参数就在get_time函数体中了 ,
get_time设置返回值 , 返回主函数的返回值 , 执行func1 ( 其实是get_time ) 时接收参数 .
主函数中返回 -- > get_time函数只能再返回 -- > func1函数的返回值 .
import time
def func1 ( num) :
print ( '程序开始执行···' )
time. sleep( 1 )
print ( num)
print ( '程序执行结束···' )
return 1 , 2 , 3
def time_func ( func) :
def get_time ( * args, ** kwargs) :
start_time = time. time( )
res = func( * args, ** kwargs)
end_time = time. time( )
print ( '程序执行时间为:%s' % ( end_time - start_time) )
return res
return get_time
func1 = time_func( func1)
res = func1( 1 )
print ( res)
这就得到了装饰器 , 在不改变被装饰对象 '内部代码' 以及 '调用方式' 的基础上为函数添加新的功能 .
* 调用方式其实是改变了只是掩饰的很好 , 很真的一样 .
2. 装饰器模板
2.1 通用模板
def outer ( func) :
def inner ( * args, ** kwargs) :
res = func( * args, ** kwargs)
return res
return inner
2.2 装饰器练习
设计一个程序 , 写一个登录验证装饰器 , 当信息校验成功后 , 才能执行主函数 .
def index ( ) :
print ( '登录成功!' )
return ''
def outer ( func) :
def inner ( * args, ** kwargs) :
name = input ( '用户名称>>>:' ) . strip( )
pwd = input ( '用户密码>>>:' ) . strip( )
if name == 'kid' and pwd == '123' :
func_res = func( * args, ** kwargs)
return func_res
else :
print ( '名称或密码错误!' )
return inner
index = outer( index)
res = index( )
3. 装饰器语法糖
语法糖 ( Syntactic sugar ) : 也译为糖衣语法 , 指计算机语言中添加的某种语法 ,
这种语法对语言的功能并没有影响 , 但是更方便程序员使用 .
使用装饰器调用被装饰的函数体代码时 , 总是需要在调用前通过赋值的方式来调用 ,
这样的方式相对比较麻烦 , 这时可以用到装饰器语法糖来节省时间和代码 .
装饰器语法糖书写规范 , 语法糖必须紧贴在装饰对象 :
@ 装饰器 # 执行到语法糖语句 , 会立刻执行 @ 后面的函数 .
被装饰的函数
装饰器语法糖内部原理 : 会自动将下面紧贴着的被装饰对象名字当作参数传递给装饰器函数调用 .
import time
def login_wraps ( func) :
def inner1 ( * args, ** kwargs) :
name = input ( '用户名称>>>:' )
pwd = input ( '用户密码>>>:' )
if name == 'kid' and pwd == '123' :
func_res = func( * args, ** kwargs)
return func_res
else :
print ( '密码错误' )
return inner1
def get_time ( func) :
def inner2 ( * args, ** kwargs) :
start_time = time. time( )
print ( '程序开始执行1···' )
res = func( * args, ** kwargs)
print ( '程序执行结束2···' )
end_time = time. time( )
print ( f'程序执行时间: { end_time - start_time} .' )
return res
return inner2
@get_time
@login_wraps
def index ( ) :
print ( '程序执行···' )
index( )
4. 装饰器修复
装饰器还存在的问题 , 查看装饰器对象和帮助信息的时候 , 就暴露了 .
为了让装饰器暴露 , 就需要修复上述问题 , 将被装饰函数的对象信息和帮助信息复制给装饰器 .
4.1 修复前
def outer ( func) :
def inner ( * args, ** kwargs) :
print ( '程序开始执行···' )
res = func( * args, ** kwargs)
print ( '程序执行结束···' )
return res
return inner
@outer
def index ( ) :
print ( '程序执行···' )
print ( index)
help ( index)
运行工具窗口显示 :
< function outer . < locals > . inner at 0x000001B7A089FC10 >
Help on function inner in module __main__ :
inner ( * args , * * kwargs )
------------------------------------------------------
查看index的信息是inner闭包函数的信息 .
4.2 修复后
Python内置functools函数工具模块 , 内置的wraps装饰器 , 可以复制被装修函的信息给装饰器 .
使用方法 :
# 1. 导入获取函数信息的装饰器 .
from functools import wraps
# 2. 作用在闭包函数上.
@ wraps ( 函数名 : 获取这个函数的信息 )
闭包函数
from functools import wraps
def outer ( func) :
@wraps ( func)
def inner ( * args, ** kwargs) :
print ( '程序开始执行···' )
res = func( * args, ** kwargs)
print ( '程序执行结束···' )
return res
return inner
@outer
def index ( ) :
print ( '程序执行···' )
print ( index)
help ( index)
运行工具窗口显示 :
< function index at 0x00000135D24FFC10 >
Help on index in module __main__ :
index ( )
5. 多层装饰器
在装饰器上在加上多个装饰器 .
使用规则 :
@ 装饰器 1
@ 装饰器 2
@ 装饰器 . . .
被装饰的函数
多层装饰器的执行顺序 :
定义阶段 调用阶段
↑ @ 装饰器 1 ↓
↑ @ 装饰器 2 ↓
↑ @ 装饰器 3 ↓
5.1 双层装饰器
import time
def login_wraps ( func) :
def inner1 ( * args, ** kwargs) :
name = input ( '用户名称>>>:' )
pwd = input ( '用户密码>>>:' )
if name == 'kid' and pwd == '123' :
func_res = func( )
return func_res
else :
print ( '密码错误' )
return inner1
def get_time ( func) :
def inner2 ( * args, ** kwargs) :
start_time = time. time( )
print ( '程序开始执行1···' )
res = func( * args, ** kwargs)
print ( '程序执行结束2···' )
end_time = time. time( )
print ( f'程序执行时间: { end_time - start_time} .' )
return res
return inner2
@get_time
@login_wraps
def index ( ) :
print ( '程序执行···' )
index( )
运行工具窗口显示 :
程序开始执行···
用户名称 > > > : kid
用户密码 > > > : 123
程序执行···
程序执行结束···
程序执行时间 : 3.755035161972046 .
5.2 三层装饰器
def outer1 ( func1) :
def wrapper1 ( * args, ** kwargs) :
print ( '执行了wrapper1' )
res1 = func1( * args, ** kwargs)
return res1
return wrapper1
def outer2 ( func2) :
def wrapper2 ( * args, ** kwargs) :
print ( '执行了wrapper2' )
res2 = func2( * args, ** kwargs)
return res2
return wrapper2
def outer3 ( func3) :
def wrapper3 ( * args, ** kwargs) :
print ( '执行了wrapper3' )
res3 = func3( * args, ** kwargs)
return res3
return wrapper3
@outer1
@outer2
@outer3
def index ( ) :
print ( 'from index' )
index( )
运行工具窗口显示 :
执行了wrapper1
执行了wrapper2
执行了wrapper3
from index
6. 有参装饰器
有参装饰器 : 是为装饰器函数传递需要的参数 .
使用闭包含的方式传值的方式为装饰器传值 .
6.1 通用模板
有参装饰器 @ 装饰器 ( '参数' ) 分三步执行 :
* 1. 第一步 , 将语法糖装饰器括号内的参数先传给装饰器上层的闭包函数 .
* 2. 第二步 , 将语法糖装饰器将被装饰的函数作为参数传给装饰器 .
* 3. 第二步 , 将内层的闭包函数作为返回值 , 使用与被装饰的函数同样的名称接收返回值 .
def dic ( vip) :
def outer ( func) :
print ( vip)
def wrapper ( * args, ** kwargs) :
ser = func( * args, ** kwargs)
return ser
return wrapper
return outer
@dic ( vip= '1' )
def index ( ) :
. . .
index( )
6.2 有参装饰器练习
def outer_par ( source_data) :
def outer ( func) :
def inner ( * args, ** kwargs) :
name = input ( '用户名称>>>:' )
pwd = input ( '用户密码>>>:' )
if source_data == 'file' :
print ( 'file文件获取用户信息.' )
if name == 'n1' and pwd == 'p1' :
func( * args, ** kwargs)
elif source_data == 'MySQL' :
print ( 'MySQL数据库获取' )
if name == 'n2' and pwd == 'p2' :
func( * args, ** kwargs)
else :
print ( '用户名或密码错误,无法执行函数' )
return inner
return outer
@outer_par ( 'file' )
def index ( ) :
print ( 'from index' )
index( )