Chapter 37 深入理解闭包与装饰器

欢迎大家订阅【Python从入门到精通】专栏,一起探索Python的无限可能!


前言

在 Python 编程中,闭包和装饰器是两个强大且灵活的概念。闭包作为一种封装函数环境的方式,为我们提供了持久的变量存储,而装饰器则通过增加额外逻辑来修改或增强现有函数的行为。本章详细讲解了这两个概念的基本原理以及实际应用。


本篇文章参考:黑马程序员

一、闭包

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

②优缺点
优点:

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

缺点:

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

③基本写法

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关键字声明这个外部变量。

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操作:

# 通过全局变量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账户状态:

# 使用闭包实现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

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

二、装饰器

①定义
装饰器是一个函数,它接受另一个函数作为参数,并返回一个新的函数。这个新的函数通常会在原函数的基础上添加一些额外的功能。
本质上,装饰器也是闭包,它可在不改变目标函数的基础上,为其增加额外功能,可以看作是在函数“外面”包裹了一层新的逻辑。

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

# 定义装饰器
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函数的行为在不修改其内部实现的情况下得到了扩展,添加了额外的行为。

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

# 定义装饰器
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()。
  • 12
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值