Python——第四章:闭包、装饰器

发布时间 2023-11-27 18:24:35作者: Magiclala

闭包:

本质, 内层函数对外层函数的局部变量的使用. 此时内层函数被称为闭包函数
    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()调用retinner函数),程序为了能够持续提供可用性,会将该段代码常驻于内存。不会被内存回收。

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

这段代码,常驻于内存,有实现内部计数器的作用。

 

装饰器

装饰器本质上是一个闭包

作用:在不改变原有函数调用的情况下. 给函数增加新的功能.(直白地说: 可以在函数前后添加新功能, 但是不改原来的代码)

哪里会用到装饰器:

  1. 程序用户登录的地方
  2. 操作日志(增、删、改、查)
  3. 可以在函数前后添加新功能, 但是不改原来的代码

 

一、装饰器推到需要用到的原理:

  1. 函数可以做为参数进行传递(代理执行)
    def func():
        print('我是函数')
    
    def gggg(fn):  # fn要求是个函数
        fn()  # func()
    
    gggg(func)
  2. 函数可以作为返回值进行返回(闭包)
    def func():
        def inner():
            print("123")
        return inner
    
    ret = func()
    ret()
  3. 函数名称可以当成变量一样进行赋值操作
    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()