闭包
1. 闭包的概念
Python 中的闭包是指延伸了作用域的函数,其中包含函数定义体中引用、但是不在定义体中定义的非全局变量。函数是不是匿名的没有关系,关键是它能访问定义体之外定义的非全局变量。
闭包是函数式编程的重要特性,它允许函数访问并操作函数外部的变量。当一个内部函数引用了外部函数的变量时,即使外部函数已经执行完毕,内部函数仍然可以访问这些变量。
def make_averager():
series = []
def averager(new_value):
series.append(new_value)
total = sum(series)
return total / len(series)
return averager在这个例子中,averager 函数引用了外部函数 make_averager 的变量 series,即使 make_averager 已经返回,averager 仍然可以访问和修改 series。
使用示例:
avg = make_averager()
print(avg(10)) # 10.0
print(avg(11)) # 10.5
print(avg(12)) # 11.02. 闭包的应用场景
2.1 数据封装和隐藏
闭包可以用来创建私有变量,实现数据封装:
def counter():
count = 0
def increment():
nonlocal count
count += 1
return count
def decrement():
nonlocal count
count -= 1
return count
def get_count():
return count
return increment, decrement, get_count
inc, dec, get = counter()
print(inc()) # 1
print(inc()) # 2
print(dec()) # 1
print(get()) # 12.2 延迟计算和缓存
闭包可以用来实现延迟计算和缓存机制:
def memoize(func):
cache = {}
def wrapper(*args):
if args not in cache:
cache[args] = func(*args)
return cache[args]
return wrapper
@memoize
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(100)) # 快速计算,因为结果被缓存2.3 配置和工厂函数
闭包可以用来创建配置化的函数:
def make_multiplier(factor):
def multiply(number):
return number * factor
return multiply
times_three = make_multiplier(3)
times_five = make_multiplier(5)
print(times_three(10)) # 30
print(times_five(10)) # 502.4 回调函数
闭包常用于创建带有特定上下文的回调函数:
def create_button_handler(button_id):
def handler():
print(f"Button {button_id} was clicked")
return handler
button1_handler = create_button_handler(1)
button2_handler = create_button_handler(2)
button1_handler() # Button 1 was clicked
button2_handler() # Button 2 was clicked3. nonlocal 关键字
在闭包中修改外部变量时,需要使用 nonlocal 关键字声明变量:
def outer():
count = 0
def inner():
nonlocal count # 声明 count 是外部变量
count += 1
return count
return inner
counter = outer()
print(counter()) # 1
print(counter()) # 2
print(counter()) # 3如果不使用 nonlocal,Python 会认为你在创建一个新的局部变量:
def outer():
count = 0
def inner():
count += 1 # 错误!UnboundLocalError
return count
return inner
counter = outer()
counter() # UnboundLocalError: local variable 'count' referenced before assignment4. 防止闭包副作用
在使用 Lambda 表达式时,如果使用了外部变量,那么在调用 Lambda 表达式时,外部变量的值会被保存,而不是在调用时获取。
def demo():
funcs = [lambda x: x + n for n in range(5)]
for f in funcs:
print(f(0))此时,输出结果为:
4
4
4
4
4这是因为所有的 lambda 函数都引用了同一个变量 n,而 n 的最终值是 4。
4.1 使用默认参数解决
如果我们希望输出结果为 0 1 2 3 4,可以使用默认参数:
def demo():
funcs = [lambda x, n=n: x + n for n in range(5)]
for f in funcs:
print(f(0))默认参数在函数定义时就被求值,因此每个 lambda 函数都有自己的 n 值。
4.2 使用 functools.partial
也可以使用 functools.partial:
from functools import partial
def demo():
funcs = [partial(lambda n, x: x + n, n) for n in range(5)]
for f in funcs:
print(f(0))4.3 使用生成器表达式
另一种方法是立即执行 lambda 表达式:
def demo():
funcs = [(lambda n: lambda x: x + n)(n) for n in range(5)]
for f in funcs:
print(f(0))5. 闭包与装饰器
装饰器本质上就是闭包的一种应用:
def timer_decorator(func):
import time
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} took {end - start:.4f} seconds")
return result
return wrapper
@timer_decorator
def slow_function():
import time
time.sleep(1)
return "Done"
slow_function() # slow_function took 1.0001 seconds6. 闭包的性能考虑
闭包会保持对外部变量的引用,这可能导致内存占用增加:
def create_large_closure():
large_data = [i for i in range(1000000)] # 大数据
def get_first():
return large_data[0]
return get_first
# large_data 会一直存在于内存中,直到 func 被销毁
func = create_large_closure()如果只需要部分数据,考虑只保存必要的信息:
def create_optimized_closure():
large_data = [i for i in range(1000000)]
first_element = large_data[0] # 只保存需要的数据
def get_first():
return first_element
return get_first7. 检查闭包变量
可以使用 __closure__ 属性查看闭包中的变量:
def outer():
x = 10
y = 20
def inner():
return x + y
return inner
func = outer()
print(func.__closure__) # (<cell at 0x...: int object at 0x...>, ...)
print([cell.cell_contents for cell in func.__closure__]) # [10, 20]8. 闭包与类的比较
闭包和类都可以用来封装状态,但各有优缺点:
# 使用闭包
def make_counter():
count = 0
def increment():
nonlocal count
count += 1
return count
return increment
# 使用类
class Counter:
def __init__(self):
self.count = 0
def increment(self):
self.count += 1
return self.count
# 使用对比
counter1 = make_counter()
counter2 = Counter()
print(counter1()) # 1
print(counter2.increment()) # 1闭包更轻量级,适合简单的状态封装;类更强大,适合复杂的对象和继承关系。