概述
它允许函数记住并访问其创建时所在的作用域链中的变量,即使该函数在其创建上下文之外执行。
闭包的核心在于,它可以捕获并保存外部函数的局部变量,使其在外部函数执行完毕后仍然可以被访问和使用。
自由变量
在一个函数中,如果使用了在该函数外部定义的变量(但不是全局变量),那么这个变量被称为自由变量。
闭包的核心就是自由变量的捕获和使用。
x = 10
def func():
print(x) # x 是自由变量
闭包的定义
如果一个嵌套函数引用了外部函数的自由变量,那么这个嵌套函数就是一个闭包。
闭包可以“记住”其创建时所在的作用域中的变量,即使外部函数已经执行完毕。
def outer():
x = 10
def inner():
print(x) # inner 函数引用了外部函数 outer 的局部变量 x
return inner
闭包形成的条件
- 嵌套函数结构:内部函数定义在外部函数体内。
- 变量引用:内部函数引用外部函数的局部变量(自由变量)。
- 返回闭包:外部函数将内部函数作为返回值或传递给其他作用域。
闭包机制
__closure__
属性
函数对象的 在 Python 中,闭包函数对象有一个 __closure__
属性,它是一个元组,包含了闭包捕获的所有自由变量的单元格对象(cell)。
每个单元格对象有一个 cell_contents 属性,用于存储被捕获的变量的值。
def outer():
x = 10
def inner():
print(x)
return inner
closure_func = outer()
print(closure_func.__closure__) # 输出:(<cell at 0x...: int object at 0x...>,)
print(closure_func.__closure__[0].cell_contents) # 输出:10
自由变量的捕获
当一个嵌套函数引用了外部函数的局部变量时,Python 会自动将这些变量捕获到闭包中。
这些变量的值会被保存在闭包的 __closure__
属性中,即使外部函数已经执行完毕。
def outer():
x = 10
y = 20
def inner():
print(x, y)
return inner
closure_func = outer()
closure_func() # 输出:10 20
应用
创建私有变量
闭包可以用来创建私有变量,这些变量只能通过闭包函数访问。
def counter():
count = 0 # 私有变量
def increment():
nonlocal count
count += 1
print(count)
return increment
# 创建闭包
my_counter = counter()
my_counter() # 输出:1
my_counter() # 输出:2
my_counter() # 输出:3
- count 是一个局部变量,定义在 counter 函数中。
- increment 函数捕获了 count 变量,并通过 nonlocal 关键字修改它。
- 每次调用 my_counter() 时,count 的值都会增加,并且这个值被保存在闭包中。
实现函数的“记忆”功能
闭包可以用来实现函数的“记忆”功能,即保存函数调用时的状态。
def adder(x):
def inner(y):
return x + y
return inner
# 创建闭包
add_five = adder(5)
print(add_five(3)) # 输出:8
print(add_five(10)) # 输出:15
- adder 函数接收一个参数 x,并返回一个闭包 inner。
- inner 函数捕获了 x 的值,并将其与传入的参数 y 相加。
- 每次调用 add_five(y) 时,x 的值始终为 5,而 y 的值根据传入的参数变化。
创建可配置的函数
闭包可以用来创建可配置的函数,这些函数的行为可以根据传入的参数动态调整。
def multiplier(factor):
def multiply(x):
return x * factor
return multiply
# 创建闭包
double = multiplier(2)
triple = multiplier(3)
print(double(5)) # 输出:10
print(triple(5)) # 输出:15
- multiplier 函数接收一个参数 factor,并返回一个闭包 multiply。
- multiply 函数捕获了 factor 的值,并将其与传入的参数 x 相乘。
- 每次调用 double(x) 或 triple(x) 时,factor 的值分别为 2 和 3,而 x 的值根据传入的参数变化。
实现简单的缓存
闭包可以用来实现简单的缓存功能,保存函数的计算结果以避免重复计算。
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 <= 1:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(10)) # 输出:55
print(fibonacci(10)) # 输出:55(直接从缓存中获取结果)
- memoize 是一个装饰器函数,它接收一个函数 func,并返回一个闭包 wrapper。
- wrapper 函数使用一个字典 cache 来保存函数的计算结果。
- 如果某个参数的计算结果已经存在于 cache 中,则直接返回缓存的结果,否则调用 func 并将结果保存到 cache 中。
最佳实践
闭包中的变量是不可变的
如果闭包捕获的变量是不可变类型(如整数、字符串等),则无法在闭包中修改其值。如果需要修改,可以使用 nonlocal 关键字。
def outer():
x = 10
def inner():
nonlocal x
x += 1
print(x)
return inner
closure_func = outer()
closure_func() # 输出:11
closure_func() # 输出:12
闭包中的变量是延迟绑定的
闭包捕获的是变量的引用,而不是变量的值。因此,如果在闭包外部修改了变量的值,闭包中捕获的变量也会相应地改变。
def outer():
x = [10]
def inner():
print(x[0])
return inner
closure_func = outer()
closure_func() # 输出:10
x = [20]
closure_func() # 输出:20
def create_functions():
funcs = []
for i in range(3):
def inner():
return i
funcs.append(inner)
return funcs
for f in create_functions():
print(f()) # 输出: 2, 2, 2
解决方案:通过参数绑定或立即求值
# 修正方案1:默认参数捕获
def create_functions_fixed():
funcs = []
for i in range(3):
def inner(i=i): # 创建局部变量i
return i
funcs.append(inner)
return funcs
# 修正方案2:使用lambda立即绑定
funcs = [lambda i=i: i for i in range(3)]
框架中的闭包
Django Middleware
它在内部使用闭包来实现中间件(Middleware)机制。中间件可以拦截请求和响应,从而实现诸如身份验证、请求日志记录等功能。
def middleware_decorator(get_response):
def middleware(request):
# 在视图函数调用之前执行的代码
print("Middleware: Before view")
response = get_response(request)
# 在视图函数调用之后执行的代码
print("Middleware: After view")
return response
return middleware
- middleware_decorator 是一个闭包,它接收一个 get_response 函数作为参数。
- 内部的 middleware 函数捕获了 get_response,并在调用 get_response(request) 之前和之后分别执行了一些代码。
- 这种机制允许中间件在请求处理的前后插入自定义逻辑。
# django/utils/deprecation.py
class MiddlewareMixin:
def __init__(self, get_response=None):
self.get_response = get_response
super().__init__()
def __call__(self, request):
response = None
if hasattr(self, 'process_request'):
response = self.process_request(request)
response = response or self.get_response(request)
if hasattr(self, 'process_response'):
response = self.process_response(request, response)
return response
闭包特性:__call__
方法实现闭包链式调用,保留中间件的处理状态
Django 视图装饰器
# django/views/decorators/http.py
def require_http_methods(request_method_list):
def decorator(func):
@wraps(func)
def inner(request, *args, **kwargs):
if request.method not in request_method_list:
return HttpResponseNotAllowed(request_method_list)
return func(request, *args, **kwargs)
return inner
return decorator
# 使用示例:闭包捕获request_method_list
@require_http_methods(["GET"])
def my_view(request):
return HttpResponse("OK")
闭包作用:decorator函数捕获外层request_method_list参数,inner函数保留对func的引用。
Flask 路由
它使用闭包来实现装饰器,从而简化路由的定义。
# flask/app.py
class Flask:
def route(self, rule, **options):
def decorator(f):
endpoint = options.pop('endpoint', None)
self.add_url_rule(rule, endpoint, f, **options)
return f
return decorator
# 使用闭包捕获rule参数
@app.route('/users')
def get_users():
return jsonify(users)
闭包机制:decorator函数捕获外层rule和options参数,实现路由绑定。
FastAPI 依赖注入系统
# fastapi/dependencies/utils.py
def Depends(dependency=None, *, use_cache=True):
def wrapper():
return dependency
# 闭包捕获dependency参数
return DependsParamWrapper(
dependency=wrapper(),
use_cache=use_cache
)
# 使用示例
async def get_db():
with Session() as session:
yield session
@app.get("/items")
async def read_items(db: Session = Depends(get_db)):
return db.query(Item).all()
闭包应用:Depends通过闭包保留依赖函数引用,实现请求级上下文管理。
asyncio 异步回调
import asyncio
def async_callback():
def callback():
print("Callback executed")
return callback
async def main():
loop = asyncio.get_event_loop()
loop.call_soon(async_callback())
await asyncio.sleep(1)
asyncio.run(main())
- async_callback 是一个闭包,它返回一个回调函数 callback。
- 在 main 函数中,使用 loop.call_soon 将回调函数注册到事件循环中。
- 当事件循环运行时,callback 函数被调用,打印一条消息。
- 这种机制允许在异步操作中插入自定义回调逻辑。
闭包的优缺点
优点
- 封装性:闭包可以封装外部函数的局部变量,使其对外部不可见,从而实现私有化。
- 灵活性:闭包可以捕获并保存外部函数的局部变量,使其在外部函数执行完毕后仍然可以被访问和使用,这为动态编程提供了很大的灵活性。
- 可配置性:闭包可以用来创建可配置的函数,这些函数的行为可以根据传入的参数动态调整。
缺点
- 复杂性:闭包的实现机制较为复杂,对于不熟悉闭包的开发者来说,可能会难以理解和维护。
- 性能开销:闭包的使用可能会导致一些性能开销,尤其是在闭包捕获大量数据或频繁调用时。
- 内存管理:闭包会捕获外部函数的局部变量,这可能会导致内存泄漏。如果闭包捕获了大量数据或长时间不释放,可能会占用过多内存。