闭包
- 名称空间
- 作用域
命名空间
大家可能已经知道Python中的函数、列表、常量等都是对象,而命名空间则是存放这些对象名称与其对象本身这一映射关系的地方。
比如string=“hello word!” 我们定义了一个str类型的值,通过string这个引用就能够访问"hello world"
这种关系就像一个字典结构,Python中可以有多个独立的命名空间,这其中不同命名空间的名称是可以重复使用的,命名空间也是分层次的。
python程序执行期间会有2个或3个活动的命名空间(函数调用时有3个,函数调用结束后2个)。按照变量定义的位置,可以划分为以下3类:
Local,局部命名空间,每个函数所拥有的命名空间,记录了函数中定义的所有变量,包括函数的入参、内部定义的局部变量。
Global,全局命名空间,每个模块加载执行时创建的,记录了模块中定义的变量,包括模块中定义的函数、类、其他导入的模块、模块级的变量与常量。
Built-in,python自带的内建命名空间,任何模块均可以访问,放着内置的函数和异常。
Local(局部命名空间)在函数被调用时才被创建,但函数返回结果或抛出异常时被删除。(每一个递归函数都拥有自己的命名空间)。
Global(全局命名空间)在模块被加载时创建,通常一直保留直到python解释器退出。
Built-in(内建命名空间)在python解释器启动时创建,一直保留直到解释器退出。
各命名空间创建顺序:python解释器启动 ->创建内建命名空间 -> 加载模块 -> 创建全局命名空间 ->函数被调用 ->创建局部命名空间
各命名空间销毁顺序:函数调用结束 -> 销毁函数对应的局部命名空间 -> python虚拟机(解释器)退出 ->销毁全局命名空间 ->销毁内建命名空间
python解释器加载阶段会创建出内建命名空间、模块的全局命名空间,局部命名空间是在运行阶段函数被调用时动态创建出来的,函数调用结束动态的销毁的。
作用域
既然有的命名空间是可以重复使用
,那么该怎么确定我想要的值在哪里呢,这里就要说到作用域了
简单举个栗子
你觉得下面这个函数输出的值是多少呢
a=1
def func():
a=5
print(a)
func()
结果是
5
为什么呢这里就要说到Python查找名称空间顺序的规则了
- LEGB规则
- Local 在一个函数或类的内部。
- Enclosed 在嵌套函数内比如闭包。
- Global 在当前模块的全局。
- Built-in 是Python为自身保留的特殊名称。
按照这个规则我们很容易就能判断出最终上面函数所输出的a的值
闭包函数
*简单来讲闭包函数就是在一个嵌套函数中,内部函数能够访问外部函数所有的参数、局部变量、及定义的其他内部函数,这样的函数在外层函数之外被调用就会形成闭包,也就是这个函数拎出来就像外面包裹着一层作用域。闭包可以看做一种传参方式
标准模板
def outer():
def inner():
pass
return inner # 注意这里返回的是inner 而不是inner()
inner 内函数的地址 inner()是值
例子
def outer():
a=100
def inner():
print(a)
return inner
a=5 # 在全局给个a=5
res=outer()
res()
# 结果
100
#这里就能很直观地理解什么叫闭包,一层作用域 包裹着这个内层函数
#无论在哪里调用这个闭包函数,a的值永远只受outer内的那个a影响
装饰器
装饰器是在不修改原函数源码及调用方式的情况下给函数扩展功能用的,装饰器也是基于闭包函数实现的。
无参装饰器
# 有一个打印函数,这时候想给他添加一个计时功能统计程序运行总体时间
# 因为这个功能被广泛调用所以要在不能修改源码及调用方式的基础上来扩展功能
import time
def outer(func): # 装饰器 添加了计算运行时间的功能 基于闭包函数
def inner():
time1=time.time() # 开始时间
func() # 调用作为外层函数参数 传入的原函数
time_total=time.time()-time1 # 程序运行总计时
print("程序运行总时间",time_total)
return inner
def prin(): # 原函数
time.sleep(2) # 休眠2秒再运行程序
print("hello world")
--------------------------------------------------------
prin=outer(prin) # 这里的 outer(prin) 其实是他返回的inner 也就是prin =inner
--------------------------------------------------------
prin() # 等于inner()
结果
hello world
程序运行总时间 2.011275291442871
可以看到已经实现了功能,并且没有改变函数源码及调用方式
被分隔的那一行可以替换成@outer 也就是语法糖,但是@outer需要写在被装饰函数的上方第一行,装饰器得写在被装饰函数的上方
import time
def outer(prin): # 参数
def inner():
time1=time.time() # 开始时间
prin() # 调用打印函数
time_total=time.time()-time1 # 程序运行总计时
print("程序运行总时间",time_total)
return inner
--------------------------------
@outer
--------------------------------
def prin():
time.sleep(2) # 休眠2秒再运行程序
print("hello world")
----------------------------------------------------
# prin=outer(prin) # 这里的 outer(prin) 其实是他返回的inner 也就是prin =inner*
----------------------------------------------------
prin() # 等于inner()
如图所示 @outer 可以代替prin=outer(prin) 这是Python语法糖非常方便
若是原函数本身有参数及返回值,那装饰器也需要进行修改
修改后如下
import time
def outer(func):
def inner(*args,**kwargs):# 传入不定长参数
time1=time.time() # 开始时间
res=func(*args,**kwargs)# 调用打印函数 同样传入不定长参数 并将返回值赋给res
time_total=time.time()-time1 # 程序运行总计时
print("程序运行总时间",time_total)
return res # 返回原函数的返回值
return inner
@outer
def prin(a):
time.sleep(2) # 休眠2秒再运行程序
print(a)
return 123
prin(12313131313) # 调用
结果
12313131313
程序运行总时间 2.0009818077087402
有参装饰器
有参装饰器
# 给原函数再加个认证功能
import time
def login_add(name,pwd):
def outer(func):
def inner(*args,**kwargs):# 传入不定长参数
if name == 1 and pwd == 2:
print("验证通过,程序运行")
time1=time.time() # 开始时间
res=func(*args,**kwargs)# 调用打印函数 同样传入不定长参数 并将返回值赋给res
time_total=time.time()-time1 # 程序运行总计时
print("程序运行总时间",time_total)
return res # 返回原函数的返回值
else:
print("输入错误!请重新登录")
return inner
return outer
@login_add(1,3) # 传了错误的参数
def prin(a):
time.sleep(2) # 休眠2秒再运行程序
print(a)
return 123
prin(11235)
#结果
输入错误!请重新登录
@login_add(1,2) # 把3改成2
prin(11235) # 再运行程序
# 结果
验证通过,程序运行
11235
程序运行总时间 2.000922203063965
叠加多个装饰器
def a(func):
def b():
func()
print("第一个装饰器")
return b
def c(func):
def d():
func()
print("第二个装饰器")
return d
@a
@c
def test():
print("原函数")
test()
# 结果
原函数
第二个装饰器
第一个装饰器
"""
# 将d看做普通参数理解
@a # --> test=a(test) 注意这里的test= d 而a(d) =b 也就是左边的test=b (由下而上)
@c # --> test=c(test) 这里的c() = d 也就是test =d
# 定义由下而上 执行从上而下 结束是从下而上
# 执行 注意看上面 @a=> test=a(test)=> a(d) => b 这里的参数func是d 而不是test 所以要先执行d
d => c(test)
也就是@a 和@b 从上而下拆开 @a=> test=a(test) => a(d) => b 于是test()=b() 而b中的func()是d()
运行d() d中的func是原函数test 那么最终执行顺序 print("原函数")
print("第二个装饰器")
print("第一个装饰器")
"""
总的来说
在函数定义阶段:执行顺序是从最靠近函数的装饰器开始,自内而外
在函数执行阶段:执行顺序由外而内,一层层执行,执行结束是由内而外