深入理解闭包与装饰器

2024-08-14 21:36:39 浏览数 (2)

一、闭包

①定义

闭包是指一个函数(内层函数)能够“记住”并访问它所在作用域的变量(外层函数的变量),即使在外层函数已经返回的情况下。

②优缺点

优点:

  • 无需定义全局变量即可实现通过函数持续地访问、修改某个值
  • 闭包使用的变量的所用于在函数内,难以被错误的调用修改

缺点:

  • 内部函数会持续引用外部函数的值,导致这一部分内存无法释放,一直占用内存

③基本写法

代码语言:python代码运行次数:0复制
def outer_function(outer_var):  
    def inner_function(inner_var):  
        函数体
    return inner_function
  • outer_function:外部函数名称,负责接收一个参数并定义一个内部函数
  • outer_var:传递给 outer_function 的一个参数。
  • inner_function:在 outer_function 内部定义的函数
  • inner_var:传递给 inner_function 的参数

整体作用:

当调用outer_function时,它会返回一个新的函数inner_function,这个新的函数“记住”了outer_var的值,inner_function可以使用这个值与传入的inner_var进行操作。

④nonlocal关键字

在闭包函数(内部函数中)想要修改外部函数的变量值时,需要用nonlocal关键字声明这个外部变量。

代码语言:python代码运行次数:0复制
def outer(num1):
    def inner(num2):
        # 声明要使用外部函数 outer 的 num1
        nonlocal num1
        num1= num2
        print(num1)
    return inner
fn=outer(10)
fn(10)
fn(10)
fn(10)

输出结果:

20

30

40

【分析】

调用 fn(10) 时,实际上是调用了 inner 函数。num1 的初始值为 10,每次调用 fn(10) 时,都会把 10 加到 num1 上,并输出更新后的值。由于 num1 是通过 nonlocal 声明的,所以每次调用都能“记住并修改 num1 的值。

【案例】

使用全局变量account_amount记录余额的简单ATM操作:

代码语言:python代码运行次数:0复制
# 通过全局变量account_amount记录余额
account_amount=0
def atm(num, deposit=True):
    global account_amount
    if deposit:
        account_amount  = num
        print(f"存款: {num}, 账户余额:{account_amount}")
    else:
        account_amount -= num
        print(f"取款:-{num}, 账户余额:{account_amount}")

atm(100)
atm(200)
atm(100, deposit=False)

运行结果:

存款: 100, 账户余额:100

存款: 200, 账户余额:300

取款:-100, 账户余额:200

【分析】

该实现简单明了,但存在全局变量带来的命名空间污染问题,容易出现变量被修改的隐患。

【改进】

通过闭包来管理ATM账户状态:

代码语言:python代码运行次数:0复制
# 使用闭包实现ATM小案例
def account_create(initial_amount=0):

    def atm(num, deposit=True):
        nonlocal initial_amount
        if deposit:
            initial_amount  = num
            print(f"存款: {num}, 账户余额:{initial_amount}")
        else:
            initial_amount -= num
            print(f"取款:-{num}, 账户余额:{initial_amount}")

    return atm

atm = account_create()

atm(100)
atm(200)
atm(100, deposit=False)

运行结果:

存款: 100, 账户余额:100

存款: 200, 账户余额:300

取款:-100, 账户余额:200

【分析】

通过使用闭包,这段代码实现了一个简单而有效的账户系统,封装了账户余额的管理,确保了数据的安全性和操作的简便性。

二、装饰器

①定义

装饰器是一个函数,它接受另一个函数作为参数,并返回一个新的函数。这个新的函数通常会在原函数的基础上添加一些额外的功能。

本质上,装饰器也是闭包,它可在不改变目标函数的基础上,为其增加额外功能,可以看作是在函数“外面”包裹了一层新的逻辑。

②写法

写法一:闭包

定义一个闭包函数, 在闭包函数内部执行目标函数并完成功能的添加。

代码语言:python代码运行次数:0复制
# 定义装饰器
def outer(func):
    def inner():
        print("我睡觉了")
        func()
        print("我起床了")
    return inner
    
# 被装饰的函数
def sleep():
    import random
    import time
    print("睡眠中……")
    time.sleep(random.randint(1,5))
    
# 应用装饰器
fn=outer(sleep)
# 调用增强后的函数
fn() # 调用 fn() 实际上是调用 inner 函数

输出结果:

我睡觉了

睡眠中……

我起床了

【分析】

通过使用闭包,装饰器的使用使得sleep函数的行为在不修改其内部实现的情况下得到了扩展,添加了额外的行为。

写法二:语法糖

使用“@”语法糖简化装饰器的定义:

代码语言:python代码运行次数:0复制
# 定义装饰器
def outer(func):
    def inner():
        print("我睡觉了")
        func()
        print("我起床了")

    return inner
    
# 使用装饰器的语法糖
@outer
def sleep():
    import random
    import time
    print("睡眠中……")
    time.sleep(random.randint(1,5))

# 调用装饰后的函数
sleep()

输出结果:

我睡觉了

睡眠中……

我起床了

【分析】

@outer是装饰器的语法糖,等价于sleep = outer(sleep)。其工作流程如下:

  • 使用 @outer 装饰 sleep 函数时,它会把 sleep 函数传递到 outer 函数中;
  • outer 返回一个 inner 函数,这个 inner 函数包含原来的 sleep 函数;
  • sleep 变量现在指向 inner 函数,所以以后的所有对 sleep() 的调用实际上都在执行 inner()。

0 人点赞