Python 闭包与装饰器:从入门到精通

大家好!今天我们来深入学习 Python 中两个紧密相关的高级特性:闭包(Closure)装饰器(Decorator)。闭包是装饰器的底层基础,理解闭包才能彻底掌握装饰器。这篇文章将带你从原理到实战,全面掌握这两个知识点。


📚 目录

  • 一、global 关键字

  • 二、闭包(Closure)

  • 三、装饰器原理

  • 四、装饰器语法糖

  • 五、带参数的装饰器

  • 六、wraps 保留元信息

  • 七、多个装饰器叠加

  • 八、装饰器带参数

  • 九、总结


一、global 关键字

1.1 基本用法

global 关键字用于在函数内部声明变量来自全局作用域,从而实现对全局变量的修改。

python 复制代码
base = 0

def sum_add(num):
    global base  # 声明 base 是全局变量
    base += num
    print(base)

sum_add(10)  # 10
sum_add(10)  # 20
sum_add(10)  # 30

1.2 为什么不推荐使用 global?

  • ❌ 破坏函数的封装性

  • ❌ 让代码难以追踪和维护

  • ❌ 容易造成意外的副作用

最佳实践:函数应该尽量独立,通过参数和返回值来传递数据,而不是依赖全局变量。


二、闭包(Closure)

2.1 什么是闭包

官方定义:内部函数引用外部函数的变量。

通俗理解 :闭包让本该被垃圾回收(GC)的变量永远留在内存中,为内部函数所使用。

python 复制代码
def fun_outer():
    num = 0
    
    def fun_inner():
        nonlocal num  # 声明 num 不是局部变量,向上查找
        num += 10
        return num
    
    return fun_inner  # 返回内部函数的引用

# f 指向 fun_inner 函数
f = fun_outer()
print(f())  # 10
print(f())  # 20
print(f())  # 30

2.2 闭包的形成条件

条件 说明
1. 函数嵌套 外部函数内部定义了内部函数
2. 返回内部函数 外部函数返回内部函数的引用
3. 引用外部变量 内部函数使用了外部函数的变量
4. 使用 nonlocal 声明该变量不是局部变量,向上查找

2.3 释放闭包

当不再需要闭包时,可以手动释放:

python 复制代码
del f      # 删除函数引用
f = None   # 置空,让垃圾回收器回收

2.4 经典面试题:函数柯里化

python 复制代码
"""
实现 add(1) -> 1
add(1)(2) -> 3
add(1)(2)(3) -> 6
add(1)(2)(3)(10) -> 16
...
"""

def add_fun(num):
    num_list = [num]
    
    def inner_add(val=None):
        if val is None:
            return sum(num_list)
        num_list.append(val)
        return inner_add
    
    return inner_add

print(add_fun(1)(2)(3)(4)(5)(1000)())  # 1015

三、装饰器原理

3.1 装饰器本质上就是闭包

装饰器 = 闭包 + 接收函数作为参数

python 复制代码
def log(fn):
    def inner(a, b):
        print("1. 方法开始执行")
        result = fn(a, b)
        print("2. 方法执行完毕")
        return result
    return inner

def add_sum(a, b):
    return a + b

# 传统方式:手动包装
f = log(add_sum)
print(f(1, 2))  # 3

3.2 装饰器的核心条件

条件 说明
1. 有嵌套函数 装饰器内部定义了内层函数
2. 返回内部函数 返回内层函数的引用
3. 引用外部函数参数 内层函数使用了被装饰的函数
4. 增加额外功能 在调用原函数前后插入逻辑

3.3 经典案例:登录检查装饰器

python 复制代码
def check_login(fn):
    def inner(val):
        print("检查当前用户是否登录!!!")
        fn(val)
    return inner

def publish_comment(content):
    print(f"我发表了:{content}")

# 手动装饰
f = check_login(publish_comment)
f("这才是真正的装饰器")

四、装饰器语法糖

4.1 传统写法 vs 语法糖

传统写法:

python 复制代码
def log(fn):
    def inner():
        print("日志记录")
        fn()
    return inner

def common():
    print("普通函数")

f = log(common)  # 手动包装
f()

语法糖写法(推荐):

python 复制代码
def log(fn):
    def inner():
        print("日志记录")
        fn()
    return inner

@log  # 语法糖,等价于 common = log(common)
def common():
    print("普通函数")

common()  # 直接调用,自动拥有装饰器功能

4.2 不带参数、不带返回值的装饰器

python 复制代码
def check_login(fn):
    def inner():
        account = input("请输入您的账号:")
        password = input("请输入您的密码:")
        if account == "admin" and password == "123456":
            fn()
        else:
            print("用户名或密码错误")
    return inner

@check_login
def publish_comment():
    print("发表评论成功!")

publish_comment()

五、带参数的通配装饰器

使用 *args**kwargs 让装饰器适配任意参数的函数。

python 复制代码
def log(fn):
    def inner(*args, **kwargs):
        print("开始执行某函数")
        res = fn(*args, **kwargs)
        print("某函数执行完毕")
        return res
    return inner

@log
def calc_all(*args, **kwargs):
    return sum(args) + sum(kwargs.values())

@log
def sum_add(a, b):
    return a + b

print(calc_all(1, 2, 3, 4, age=18, money=2000))
print(sum_add(1, 2))

六、wraps 保留元信息

装饰器会丢失 原函数的元信息(如 __name____doc__ 等)。使用 @wraps 可以保留这些信息。

python 复制代码
from functools import wraps

def check_code(fn):
    @wraps(fn)  # ✅ 保留原函数的元信息
    def inner(*args, **kwargs):
        code = input("请输入您的验证码:")
        if code == "1234":
            return fn(*args, **kwargs)
        else:
            print("验证码错误")
    return inner

@check_code
def login():
    """用户登录函数"""
    print("用户开始登录")

# 即使被装饰,依然保留原始信息
print(login.__name__)  # login(没有 @wraps 会变成 inner)
print(login.__doc__)   # 用户登录函数

七、多个装饰器叠加

7.1 执行顺序

多个装饰器叠加时,从下往上执行(靠近函数的先执行)。

python 复制代码
from functools import wraps

def log(fn):
    @wraps(fn)
    def inner(*args, **kwargs):
        print(f"{fn.__name__}: 开始执行")
        res = fn(*args, **kwargs)
        print(f"{fn.__name__}: 执行完毕")
        return res
    return inner

def check_user(fn):
    @wraps(fn)
    def inner(*args, **kwargs):
        user_name = input("请输入您的用户名:")
        if user_name == 'admin':
            return fn(*args, **kwargs)
        else:
            print("用户名输入错误")
    return inner

def check_code(fn):
    @wraps(fn)
    def inner(*args, **kwargs):
        code = input("请输入您的验证码:")
        if code == '1234':
            return fn(*args, **kwargs)
        else:
            print("验证码输入错误")
    return inner

# 执行顺序:log → check_user → check_code(从下往上)
@log
@check_user
@check_code
def publish(user_name, content):
    print(f"{user_name}: 发表内容: {content}")

publish("admin", "AI时代人人可编程")

八、装饰器带参数

当装饰器本身需要接收参数时,需要三层嵌套

python 复制代码
import time

def check_time_out(name, ms=False):
    def check_time(fn):
        def inner(*args, **kwargs):
            begin = time.time()
            res = fn(*args, **kwargs)
            end = time.time()
            cost = end - begin
            if ms:
                print(f'{name}程序耗时: {cost * 1000:.2f} 毫秒')
            else:
                print(f'{name}程序耗时: {cost:.2f} 秒')
            return res
        return inner
    return check_time

@check_time_out("zhangsan", ms=True)
def big_loop():
    total = 0
    for i in range(1000000):
        total += i

big_loop()

九、总结

9.1 闭包 vs 装饰器

概念 核心要点
闭包 内部函数 + 外部变量 + nonlocal
装饰器 闭包 + 接收函数作为参数 + 语法糖 @

9.2 装饰器核心知识点速记

知识点 要点
闭包条件 嵌套 + 返回 + 引用外部变量 + nonlocal
装饰器本质 闭包,接收函数,返回增强后的函数
语法糖 @装饰器名 简化写法
通配参数 *args**kwargs
保留元信息 @wraps(fn)
执行顺序 从下往上(靠近函数的先执行)
带参数装饰器 三层嵌套

📝 最后提醒:闭包是装饰器的底层基础,理解闭包才能真正掌握装饰器。多练习、多思考,自然就能灵活运用!

如果觉得有帮助,欢迎点赞、收藏、评论! 😊

相关推荐
未来之窗软件服务1 分钟前
计算机二级[英文]-C 字符串移动—东方仙盟
c语言·开发语言·仙盟创梦ide·东方仙盟
Wang's Blog3 分钟前
Java框架快速入门: Spring Security+OAuth2之过滤器与过滤器链
java·开发语言·spring
谢尔登23 分钟前
01_Java基础速通
java·开发语言
前端炒粉24 分钟前
DOM 常用 API 速查表
开发语言·javascript·ecmascript
枫彩44 分钟前
A股数据源怎么选?用 Python 验收 AI 复盘的日期与明细
python·ai agent·股票数据·mcp
2601_962382431 小时前
Python 代码不可不知的函数式编程技术
python·函数式编程·高阶函数·嵌套函数·头等函数
2601_962078031 小时前
Python办公自动化与数据分析实战 培训班2021年
python·数据分析·办公自动化·自动化办公·实战演练
程序员梅雨1 小时前
Linux & Shell 实用干货
linux·运维·服务器·后端·面试·php
用户938515635071 小时前
Text2SQL 实战:用 DeepSeek 大模型实现自然语言查询 SQLite 数据库
python·sqlite
2601_962099682 小时前
Python环境下中文分词实现与应用探索
python·自然语言处理·中文分词·jieba·开源社区