闭包:
本质, 内层函数对外层函数的局部变量的使用. 此时内层函数被称为闭包函数
1. 可以让一个变量常驻与内存,可随时被外层函数调用。
2. 可以避免全局变量被修改、被污染、更安全。(通过版本控制工具,将不同人所写的代码都整合的时候,避免出现问题)
def func():
a = 10
def inner():
print(a)
return a
return inner
ret = func()
代码定义了一个函数 func
,它返回了另一个函数 inner
。这种结构被称为闭包(closure),因为 inner
函数引用了在其外部定义的变量 a
。在这里,a
是 func
函数的局部变量,但由于 inner
函数引用了它,a
的值在 inner
函数被调用时仍然是可用的。
这段代码可以实现的特殊效果:
1、因为迟迟没有使用ret()
调用ret
(inner
函数),程序为了能够持续提供可用性,会将该段代码常驻于内存。不会被内存回收。
2、用函数来定义局部变量a,并且局部变量不会被后续函数外的代码操控、改变,仅可以被赋值后读取、打印。不能被其他全局变量修改。实现局部变量仅可以在本函数内部才可以被操作。a被保护起来了。
3、想调用这个局部变量a,可以随时调用ret()
函数,使得局部变量既可以被调用,还不会被修改。
未来某一时刻,ret()
调用了 func
函数并将其结果赋值给变量 ret
。此时,ret
包含了 inner
函数。如果你调用 ret()
,它将输出 10
,因为 inner
函数引用了外部的 a
变量,而 a
的值在 func
函数被调用时被设置为 10
。
再看下面一段代码
def func():
a = 0
def inner():
nonlocal a
a += 1
return a
return inner
ret = func()
a = 20 #此时即使出现了全局变量a=20,也不会干扰到func函数局部变量a的计数器累计
# inner => ret => 什么时候执行
r1 = ret()
print(r1) #第一次输出,结果为1
# 可能1000000行后才执行
r2 = ret()
print(r2) #第二次输出,结果为2
print(ret()) #结果为3
print(ret()) #结果为4
这段代码,常驻于内存,有实现内部计数器的作用。
装饰器
装饰器本质上是一个闭包
作用:在不改变原有函数调用的情况下. 给函数增加新的功能.(直白地说: 可以在函数前后添加新功能, 但是不改原来的代码)
哪里会用到装饰器:
- 程序用户登录的地方
- 操作日志(增、删、改、查)
- 可以在函数前后添加新功能, 但是不改原来的代码
一、装饰器推到需要用到的原理:
- 函数可以做为参数进行传递(代理执行)
def func(): print('我是函数') def gggg(fn): # fn要求是个函数 fn() # func() gggg(func)
- 函数可以作为返回值进行返回(闭包)
def func(): def inner(): print("123") return inner ret = func() ret()
- 函数名称可以当成变量一样进行赋值操作
def func1(): print("我是函数1") def func2(): print("我是函数2") func1 = func2 func1()
二、装饰器的推导过程:
1.定义2个游戏,运行2个游戏
def play_dnf():
print("开始玩dnf游戏")
def play_lol():
print("开始玩lol游戏")
play_dnf()
play_lol()
2.此时我们出现了新的需求:
- 在运行dnf游戏前,先打开外挂;在结束游戏后,关闭外挂。
- 在运行lol游戏前,先打开外挂;在结束游戏后,关闭外挂。
- 我们要用游戏管家自动搞这2个事情
因此我们又试图定义了一个管家,并且用代理执行的逻辑(函数可以做为参数进行传递)传输函数执行.
def guanjia(game):
print("打开外挂")
game() # 玩起来了
print('关闭外挂')
def play_dnf():
print("开始玩dnf游戏")
def play_lol():
print("开始玩lol游戏")
guanjia(paly_dnf)
guanjia(paly_lol)
但是我们运行后发现:打游戏的主题不是我,而是管家 。这样就非常的不合理。因为运行的主体变了。我们只想要管家负责在我玩游戏之前开外挂、我玩游戏之后关外挂,而不是代理打游戏。
因此我们使用了:
1、闭包的玩法(函数可以作为返回值进行返回);
2、函数名称可以当成变量一样进行赋值操作,再次对游戏进行封装;
def guanjia(game):
def inner():
print("打开外挂")
game() # 玩起来了
print('关闭外挂')
return inner
def play_dnf():
print("开始玩dnf游戏")
def play_lol():
print("开始玩lol游戏")
play_dnf = guanjia(play_dnf) # 让管家把游戏重新封装一遍. 我这边把原来的游戏替换了
play_lol = guanjia(play_lol) # 让管家把lol也重新封装一下.
play_dnf() # 此时运行的是管家给的内层函数inner
play_lol()
解读:这里是把play_dnf
(实参)传给game
(形参),然后用return
返回inner
,把打开外挂、玩游戏、关闭外挂一切都返回给(全新的)打包的play_dnf = guanjia(play_dnf)
,让一切都是我执行的。
这样操作就把原先的管家打游戏,变回了我打游戏,只是这一切都是“我自己操作的”。主体没有发生变化。
注意:
因为这种写法,play_dnf = guanjia(play_dnf)
非常不容易阅读,容易造成混淆,并且有更好的替代写法——在运行程序的前面进行标记(@guanjia
),每次你要运行程序之前,guanjia就会自动加载封装的代码。
def guanjia(game):
def inner():
print("打开外挂")
game() # 玩起来了
print('关闭外挂')
return inner
@guanjia
def play_dnf():
print("开始玩dnf游戏")
@guanjia # 相当于 play_dnf = guanjia(play_lol)
def play_lol():
print("开始玩lol游戏")
play_dnf() # 因为前面有@guanjia,这里就相当于 play_dnf = guanjia(play_dnf)
play_lol() # 因为前面有@guanjia,这里就相当于 play_dnf = guanjia(play_lol)
至此,我们搞到了装饰器的雏形:
def wrapper(fn): #wrapper: 装饰器, fn: 目标函数
def inner:
pass # 在目标函数执行之前操作
fn() # 执行目标函数
pass # 在目标函数执行之后操作
return inner #千万别加()
@wrapper
def target(): #目标函数
pass
target() # => inner()