python闭包详解
1、 闭包的基本概念
闭包是指在一个函数内部定义另一个函数,并且内层函数引用了外层函数的变量。当外层函数执行完毕后,它的作用域中的变量被内层函数 "捕获",并能继续访问,这个被捕获的环境称为闭包。
闭包的核心思想:
- 数据封装:将数据和操作数据的函数封装在一起,形成一个“小型类”。
- 延迟执行:闭包保存了外部作用域变量,直到内层函数被调用时才会使用这些变量。
- 减少全局变量:避免使用全局变量,防止命名冲突,提高代码的可维护性。
2、 闭包的用途和用法
闭包的常见用途:
- 数据隐藏:封装变量,形成私有数据。
- 保存状态:闭包可以记住外层函数的环境变量,提供延迟计算的能力。
- 回调函数:闭包可以用作回调函数,在事件驱动或异步编程中非常常见。
- 工厂函数:通过闭包动态生成多个不同功能的函数。
闭包的创建需要满足以下三个条件:
- 在一个函数内部定义另一个函数。
- 内层函数引用了外层函数的变量。
- 外层函数返回内层函数。
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、 闭包的特点
- 保存外层作用域变量:即使外层函数执行完毕,变量依然存在于闭包中。
- 减少全局变量:闭包避免了全局变量的使用,提供更高的代码封装性。
- 状态保持:每个闭包实例都会保存自己的独立状态。
- 动态生成函数:闭包可以根据输入参数动态生成不同功能的函数。
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 在函数式编程和面向对象编程之间实现了完美平衡,简化了代码逻辑,提高了代码的可读性和复用性。