当前位置: 首页 > article >正文

python闭包详解

1、 闭包的基本概念

闭包是指在一个函数内部定义另一个函数,并且内层函数引用了外层函数的变量。当外层函数执行完毕后,它的作用域中的变量被内层函数 "捕获",并能继续访问,这个被捕获的环境称为闭包

闭包的核心思想:

  • 数据封装:将数据和操作数据的函数封装在一起,形成一个“小型类”。
  • 延迟执行:闭包保存了外部作用域变量,直到内层函数被调用时才会使用这些变量。
  • 减少全局变量:避免使用全局变量,防止命名冲突,提高代码的可维护性。

2、 闭包的用途和用法

闭包的常见用途:

  1. 数据隐藏:封装变量,形成私有数据。
  2. 保存状态:闭包可以记住外层函数的环境变量,提供延迟计算的能力。
  3. 回调函数:闭包可以用作回调函数,在事件驱动或异步编程中非常常见。
  4. 工厂函数:通过闭包动态生成多个不同功能的函数。

闭包的创建需要满足以下三个条件:

  1. 在一个函数内部定义另一个函数。
  2. 内层函数引用了外层函数的变量。
  3. 外层函数返回内层函数。

2.1 最简单的闭包

def outer_function(message):

    def inner_function():

        print(message)  # 引用了外部函数的变量 message

    return inner_function

# 使用闭包
closure = outer_function("Hello, Python!")

closure()  # 输出:Hello, Python!

解释:

  • inner_function 在定义时引用了 outer_function 中的变量 message。
  • 即使 outer_function 执行完毕,message 依然保存在闭包中。

2.2 使用闭包实现数据封装(计数器)

def counter():

    count = 0  # 封装的状态变量

    def increment():

        nonlocal count  # 使用 nonlocal 声明,修改外部作用域变量

        count += 1

        return count

    return increment

# 创建闭包实例
counter1 = counter()
print(counter1())  # 输出:1
print(counter1())  # 输出:2
print(counter1())  # 输出:3

counter2 = counter()  # 新的计数器实例
print(counter2())  # 输出:1

解释:

  • count 是外部函数的局部变量,被 increment 引用。
  • nonlocal 关键字使得 count 可以被修改。
  • 每个 counter() 调用都会创建一个新的闭包实例,独立保存状态。

3、 闭包的应用场景

3.1 数据隐藏

闭包可以用于创建私有变量,使数据无法被外部直接访问,只能通过函数接口修改。

def bank_account(initial_balance):

    balance = initial_balance  # 私有变量

    def deposit(amount):

        nonlocal balance

        balance += amount

        return balance

    def withdraw(amount):

        nonlocal balance

        if amount > balance:

            print("Insufficient funds")

        else:

            balance -= amount

        return balance

    return deposit, withdraw

# 创建银行账户
deposit, withdraw = bank_account(100)

print(deposit(50))  # 输出:150
print(withdraw(30))  # 输出:120
print(withdraw(200))  # 输出:Insufficient funds 
#最后输出余额:120

解释:

  • balance 是封装的数据,只能通过 deposit 和 withdraw 进行访问和修改。

3.2 工厂函数(动态创建函数)

闭包可以动态生成不同功能的函数。

def multiplier(factor):

    def multiply(number):

        return number * factor

    return multiply

# 创建不同倍数的乘法函数
double = multiplier(2)
triple = multiplier(3)

print(double(5))  # 输出:10
print(triple(5))  # 输出:15

解释:

  • factor 是外部函数的变量,通过闭包传入不同值生成不同的乘法函数。

3.3 回调函数

闭包常用于事件驱动编程中的回调函数。

def create_callback(message):

    def callback():

        print(f"Callback executed with message: {message}")

    return callback

# 注册回调
callback1 = create_callback("Event 1 triggered")
callback2 = create_callback("Event 2 triggered")

callback1() #输出 Callback executed with message: Event 1 triggered
callback2() #输出 Callback executed with message: Event 2 triggered

输出:

Callback executed with message: Event 1 triggered

Callback executed with message: Event 2 triggered

3.4 延迟计算

闭包保存了计算状态,按需执行。

def lazy_sum(*args):

    def calc_sum():

        return sum(args)

    return calc_sum

# 创建延迟计算的函数
sum_later = lazy_sum(1, 2, 3, 4, 5)
print(sum_later())  # 输出:15

解释:

  • args 被闭包保存,当需要结果时才计算。

4、 闭包的特点

  1. 保存外层作用域变量:即使外层函数执行完毕,变量依然存在于闭包中。
  2. 减少全局变量:闭包避免了全局变量的使用,提供更高的代码封装性。
  3. 状态保持:每个闭包实例都会保存自己的独立状态。
  4. 动态生成函数:闭包可以根据输入参数动态生成不同功能的函数。

5、 使用闭包时应注意

nonlocal 关键字:如果内层函数需要修改外层函数的变量,必须使用 nonlocal 关键字。否则变量被视为局部变量,导致 UnboundLocalError。

闭包占用内存:闭包会保存外层作用域变量,可能导致不必要的内存占用,注意释放闭包。

变量延迟绑定问题:在闭包中使用循环变量时,变量的值会在执行时绑定,导致意外结果。

示例:变量延迟绑定问题

def make_functions():

    funcs = []

    for i in range(3):

        def inner():

            return i  # 引用循环变量

        funcs.append(inner)

    return funcs

funcs = make_functions()
print([f() for f in funcs])  # 输出:[2, 2, 2]

解决方法:使用默认参数绑定当前值

def make_functions_fixed():

    funcs = []

    for i in range(3):

        def inner(i=i):  # 使用默认参数绑定当前值

            return i

        funcs.append(inner)

    return funcs

funcs = make_functions_fixed()
print([f() for f in funcs])  # 输出:[0, 1, 2]

闭包是一种强大的编程工具,具有以下优势:

  • 数据封装:通过函数隐藏数据,实现类似私有变量的功能。
  • 状态保持:保存外层函数的变量状态,形成独立的作用域。
  • 动态函数生成:根据输入创建不同功能的函数。
  • 回调与延迟执行:闭包可以作为回调函数或延迟执行的工具。

闭包的设计思想使 python 在函数式编程和面向对象编程之间实现了完美平衡,简化了代码逻辑,提高了代码的可读性和复用性。


http://www.kler.cn/a/452771.html

相关文章:

  • 牛客周赛73B:JAVA
  • Type-C单口便携显示器LDR6021
  • 前端网页开发学习(HTML+CSS+JS)有这一篇就够!
  • Y3编辑器教程8:资源管理器与存档、防作弊设置
  • JAVAweb学习日记(三)Ajax
  • Marscode AI辅助编程
  • 2024年第十一期 | CCF ODC《开源战略动态月报》
  • 使用Python开发高级游戏:实现一个3D射击游戏
  • UE5仿漫威争锋灵蝶冲刺技能
  • ElasticSearch 的工作原理
  • Springboot + vue3 实现大文件上传方案:秒传、断点续传、分片上传、前端异步上传
  • 医药垃圾分类管理系统|Java|SSM|JSP|
  • Intent--组件通信
  • 华为认证考试模拟题测试题库(含答案解析)
  • STM32-笔记10-手写延时函数(SysTick)
  • nacos-服务发现注册
  • 【Linux】shell脚本:查找可执行文件和批量创建多个账户
  • LabVIEW实现NB-IoT通信
  • Pillow库
  • arXiv-2024 | STMR:语义拓扑度量表示引导的大模型推理无人机视觉语言导航
  • Vuex 的使用和原理详解
  • android 手工签名,(电子签名)
  • windows C#-编写复制构造函数
  • 掌握Go语言:配置环境变量、深入理解GOPATH和GOROOT(1)
  • Java中String类型的字符串转换成JSON对象和JSON字符串
  • [STM32] 串口通信 (十一)