1、闭包的核心条件(以 Python 为例)

1.1、要形成闭包,必须满足三个条件:

  • 存在嵌套函数(内部函数定义在外部函数内部);
  • 内部函数引用了外部函数作用域中的变量(注意非全局变量);
  • 外部函数返回内部函数对象(而非内部函数的执行结果)。

1.2、直接看代码

def outer_func(out_var):
    # 外部函数定义变量 out_var
    def inner_func():
        # 内部函数引用外部函数的变量
        print(f"我能访问外部函数的变量:{out_var}")
    # 外部函数返回内部函数(不执行,只返回函数对象)
    return inner_func

# 调用外部函数,得到内部函数对象,此时 outer_func 已执行完毕
closure = outer_func("闭包测试变量")
# 调用内部函数,依然能访问外部函数的变量
closure()  # 输出:我能访问外部函数的变量:闭包测试变量

我们一步一步看,先看闭包函数的定义:

def outer_func(out_var):   # 定义了外部函数
    # 外部函数定义变量 out_var
    def inner_func():
        # 内部函数引用外部函数的变量
        print(f"我能访问外部函数的变量:{out_var}")
    # 外部函数返回内部函数(不执行,只返回函数对象)
    return inner_func

调用过程中,先直接调用外部函数,然后因为外部函数返回了内部函数的函数名,所以此时使用closure也能调用 inner_func函数。

我们再来看一下另外一个例子:

这个例子,是内层和外层函数都需要传入参数,并且这两个参数都需要再内层函数中使用

2、闭包应用的实际场景

场景 1:轻量级 “无类封装”(替代简单类)

如果只需要封装少量状态(1-2 个变量),不需要定义完整的类,闭包是更简洁的选择:

对比:类 vs 闭包

python

# 用类实现(繁琐)
class Printer:
    def __init__(self, prefix):
        self.prefix = prefix
    def print_msg(self, msg):
        print(f"[{self.prefix}] {msg}")

info_printer = Printer("INFO")
info_printer.print_msg("程序启动")

# 用闭包实现(简洁)
def make_printer(prefix):
    def printer(msg):
        print(f"[{prefix}] {msg}")  # 保留prefix状态
    return printer

info_printer = make_printer("INFO")
info_printer("程序启动")  # 调用更简单,无需写.print_msg()

工程价值:减少模板代码,轻量级封装状态,代码更简洁。


🔍 场景 2:实现装饰器(Python 最核心的应用)

装饰器是闭包最经典的实战场景,用于「无侵入增强函数功能」(日志、缓存、权限校验、计时等):

示例:带缓存的装饰器(避免重复计算)

python

运行

def cache_decorator(func):
    cache = {}  # 闭包保留的缓存状态,所有调用共享
    def wrapper(*args):
        if args not in cache:
            cache[args] = func(*args)  # 首次计算并存缓存
        return cache[args]
    return wrapper

@cache_decorator
def calc_sum(n):
    # 模拟耗时计算:1+2+...+n
    return sum(range(n+1))

# 第一次计算,存入缓存
print(calc_sum(1000000))
# 第二次直接取缓存,无需重复计算
print(calc_sum(1000000))

其实这里的信息量还是很大的,关键要弄清楚cache字典存在的周期:cache 的生命周期 = wrapper 函数的生命周期,只要 wrapper 还能被访问到,cache 就活着;wrapper 被回收,cache 就跟着走。

工程价值

  • 无侵入增强:原函数 calc_sum 无需修改;
  • 状态复用:缓存 cache 被所有调用共享,避免重复开销。

🔍 场景 3:实现 “有状态的函数”(替代全局变量)

如果函数需要记住之前的执行状态,但不想用全局变量(全局变量易污染、难维护),闭包是最优解:

示例:计数器(记住累计次数)

python

def make_counter():
    count = 0  # 闭包保留的状态,外部无法直接修改
    def counter():
        nonlocal count  # 声明修改外层变量
        count += 1
        return count
    return counter

# 两个计数器独立保留状态
counter1 = make_counter()
counter2 = make_counter()

print(counter1())  # 1
print(counter1())  # 2
print(counter2())  # 1(和counter1互不干扰)

工程价值

  • 避免全局变量:count 只在闭包内可见,不会被其他代码意外修改;
  • 状态隔离:每个计数器独立保留自己的 count,互不干扰。
  • 注意这里的关键字nonlocal,声明后,在内部函数中修改外部函数中定义的变量变量

🔍 场景 4:延迟执行 / 惰性加载(提升程序性能)

闭包可以把 “计算逻辑” 和 “执行时机” 分开,实现按需加载:

示例:懒加载配置文件(首次使用时才读取)

python

def lazy_load_config(file_path):
    config = None  # 初始为None,不加载
    def load_config():
        nonlocal config
        if config is None:
            # 首次调用时才读取文件(延迟执行)
            with open(file_path, "r") as f:
                config = f.read()
        return config
    return load_config

# 初始化时不读取文件,提升启动速度
config_loader = lazy_load_config("config.ini")
# 只有第一次使用时才读取文件
print(config_loader())
# 后续调用直接返回缓存的config
print(config_loader())

工程价值:避免程序启动时加载大量不必要的资源,提升启动速度。


🔍 场景 5:实现策略模式(动态切换业务逻辑)

闭包可以封装不同的业务策略,根据场景返回不同的策略函数,且保留策略的配置状态:

示例:动态生成不同的校验规则

python

def make_validator(min_len, max_len):
    # 封装校验规则的配置(min_len/max_len)
    def validator(text):
        return min_len <= len(text) <= max_len
    return validator

# 生成不同的校验器:手机号校验(11位)、密码校验(6-16位)
phone_validator = make_validator(11, 11)
pwd_validator = make_validator(6, 16)

print(phone_validator("13800138000"))  # True
print(pwd_validator("123456"))         # True
print(pwd_validator("123"))            # False

工程价值:一次定义规则模板,多次生成不同配置的校验器,代码复用性高。


🔍 场景 6:异步 / 回调函数(保留上下文)

在异步编程、事件监听中,闭包可以保留回调函数的上下文(比如请求参数、用户信息):

示例:异步请求的回调(保留请求 ID)

python

import time
import threading

def async_request(url):
    request_id = f"req-{int(time.time())}"  # 生成唯一请求ID
    def callback(response):
        # 闭包保留request_id,回调时能关联请求和响应
        print(f"请求[{request_id}] {url} 响应:{response}")
    # 模拟异步执行:启动线程处理请求
    threading.Thread(target=lambda: callback("success")).start()

# 发起两个异步请求,回调能正确关联各自的request_id
async_request("https://api.example.com/user")
async_request("https://api.example.com/order")

工程价值:回调函数能记住发起请求时的上下文(request_id/url),避免用全局变量传递状态。


🎯 闭包的核心优势(工程视角)

表格

优势对比方案价值
轻量级封装少写 __init__/self 等模板代码,简洁高效
状态隔离全局变量状态只在闭包内可见,避免污染和意外修改
延迟执行立即执行按需加载资源,提升程序启动速度和运行效率
无侵入增强修改原函数装饰器模式,不破坏原函数逻辑,符合开闭原则

总结

  1. 闭包的核心是保留外层函数的状态,且状态对外隔离
  2. 工程中最常用的场景:装饰器、轻量级状态封装、延迟执行、策略模式、异步回调;
  3. 适用场景:需要 “函数级别的状态复用”,但又不想定义类 / 用全局变量的场景。
Logo

这里是“一人公司”的成长家园。我们提供从产品曝光、技术变现到法律财税的全栈内容,并连接云服务、办公空间等稀缺资源,助你专注创造,无忧运营。

更多推荐