闭:定义在函数内部的函数
包:内层函数使用了外层函数名称空间中的名字
def outer(): x = 999 def inner(): print('外层函数的内层函数inner', x) # 内层函数使用到了外层函数名称空间中名字 return inner x = 666 res = outer() # 得到的返回值是函数名inner res() # 相当于inner() # 外层函数的内层函数inner 999
闭包函数是给函数体传参的另外一种方式
函数体传参的方式1:形参
def f1(name): print(name) # 函数体代码需要什么就可以在形参中写什么 f1('LeoMessi')
函数体传参的方式2:闭包
# 闭包传参的方式1:外层函数的变量值固定 def outer(): name = 'LeoMessi' def inner(): print(name) # 永远使用的都是外层函数固定的变量值LeoMessi return inner res = outer() res() # 相当于inner() # LeoMessi
概念:在不改变被装饰对象原有的'调用方式'和'内部代码'的情况下,给被装饰对象添加新的功能
装饰器的原则:对扩展开放,对修改封闭
需求:统计函数的执行时间
# 预备知识:time模块 import time print(time.time()) # 时间戳,1970年1月1日0时0分0秒距离刚刚代码运行间隔的秒数
实现方法1:(方法1没有修改被装饰对象的调用方式,也没修改内部代码,但可扩展性差) import time def f1(): time.sleep(3) print('f1函数已运行') start_time = time.time() # 函数执行之前获取一个时间戳 f1() end_time = time.time() # 函数执行之后获取一个时间戳 print(end_time - start_time) # 两个时间戳的差值就是函数的执行时间 # f1函数已运行 # 3.0044925212860107
需求:如果在很多地方都需要调用f1,如何统计f1的执行时间,即在很多地方都需要执行统计f1执行时间的代码,就用到了函数
实现方法2:(函数的实现方式:将函数名通过形参传入,封装成函数之后,调用方式变了,不符合装饰器原则) import time def f1(): time.sleep(3) print('f1函数已运行') def get_time(func): start_time = time.time() func() end_time = time.time() print(end_time - start_time) get_time(f1)
方法2给函数体直接传参无法实现装饰器,采用另一种给函数体传参的方式(闭包函数)
2 res=get_time函数指向的函数体代码 3 func=函数体f1指向的函数体代码 7 执行get_time函数体代码 9 执行f1函数体代码
为了解决方法3外层函数变量值固定的问题,采用向外层函数传参的方式
1和2会在全局名称空间中产生两个名字f1和outer分别指向两个函数体代码 6使用一个变量名接收outer函数的返回值,该变量名与全局函数名f1冲突了: 里面的f1函数名就被普通的变量名f1替换了(全局只有一个普通的变量名f1) 7通过f1指向的是get_time函数体代码 9执行的才是真正的f1函数
# 实现方法6:(观察被装饰函数的返回值) import time def f1(a, b): time.sleep(1) print(a, b, 'f1函数已运行') return '哈哈,和想象的有点不一样吧' res = f1(11, 22) print(res) # 哈哈,和想象的有点不一样吧 def outer(func_name): def get_time(*args, **kwargs): start_time = time.time() func_name(*args, **kwargs) end_time = time.time() print(end_time - start_time) return get_time f1 = outer(f1) res = f1(11, 22) # 接收函数的返回值 print(res) # None(因为get_time函数没有返回值,想要在这里实现与直接调用函数相同的效果,失败)
装饰器的经典模板
def f1(*args, **kwargs): pass def outer(func_name): # func_name用于接收被装饰的对象(函数) def inner(*args, **kwargs): print('执行被装饰函数之前,可以做的额外操作') res = func_name(*args, **kwargs) # 执行真正的被装饰函数,res的作用是接收原函数被调用后的返回值 print('执行被装饰函数之后,可以做的额外操作') return res # 返回真正函数的返回值 return inner f1 = outer(f1) f1()
# 实战案例:校验用户信息是否正确 def f1(*args, **kwargs): print('这是f1函数,只有LeoMessi才可以调用') def outer(func_name): # func_name用于接收被装饰的对象(函数) def inner(*args, **kwargs): username = input('请输入用户名:') # 执行f1函数之前,先获取用户数据 password = input('请输入密码:') if username == 'LeoMessi' and password == '30': res = func_name(*args, **kwargs) # 执行真正的被装饰函数,res的作用是接收原函数被调用后的返回值 return res else: print('Sorry, no qualification') return inner f1 = outer(f1) f1()
作用:语法糖仅仅是让代码变得更加简洁
语法糖内部原理:1.使用的时候最好紧跟在被装饰对象的上方
2.语法糖会自动将下面紧挨着的函数名传给@后面的函数调用
def outer(func_name): # 装饰器 def inner(*args, **kwargs): print('执行原函数之前的额外操作') res = func_name(*args, **kwargs) print('执行原函数之后的额外操作') return res return inner @outer # 等价于f1 = outer(f1) def f1(*args, **kwargs): # 原函数 print('函数f1已运行') f1() # 执行函数之前的额外操作 # 函数f1已运行 # 执行函数之后的额外操作 @outer # 等价于f2 = outer(f2) def f2(*args, **kwargs): # 原函数 print('函数f2已运行') f2() # 执行函数之前的额外操作 # 函数f2已运行 # 执行函数之后的额外操作
做到和真的一模一样,但是本质其实没有变