闭包与装饰器:Python 函数式编程的 “黑魔法”

目录

前言

一、闭包:函数 “记住” 外部变量的魔法

1. 什么是闭包?

2.闭包的基本示例

3.闭包的核心作用(保存状态)

4. 闭包的条件

二、装饰器:基于闭包的 “功能增强” 神器

1. 什么是装饰器?

2. 装饰器的基本用法:给函数 “加功能”

3. 装饰器的工作原理

4. 进阶:装饰带参数的函数

5. 带参数的装饰器:让装饰器更灵活

6. 修复原函数的 “身份信息”

三、闭包与装饰器的关系:装饰器是闭包的 “特殊应用”

四、实际应用场景:装饰器的 “超能力”

1. 日志记录

2. 权限验证

3. 缓存结果(减少重复计算)

总结


前言

在 Python 中,闭包和装饰器是函数式编程的精髓,它们能让代码更简洁、更灵活,尤其在 “不修改原函数” 的前提下为其添加功能时堪称 “神器”。本文从概念到实战,带你吃透这两个看似抽象的知识点。

一、闭包:函数 “记住” 外部变量的魔法

1. 什么是闭包?

闭包是嵌套函数的一种特殊形式:

  • 内部函数引用了外部函数的变量(非全局变量);
  • 外部函数返回了内部函数。

简单说,闭包让内部函数 “记住” 了它诞生时的环境(外部变量的值)。

2.闭包的基本示例

def outer_func(outer_var):
    # 外部函数定义一个变量 outer_var
    
    def inner_func(inner_var):
        # 内部函数引用外部变量 outer_var
        return outer_var + inner_var
    
    # 外部函数返回内部函数(不加括号,返回函数本身)
    return inner_func


# 调用外部函数,得到内部函数对象,此时 outer_var 被“记住”
closure = outer_func(10)

# 调用内部函数,传入 inner_var
print(closure(5))  # 输出:15(10 + 5)
print(closure(8))  # 输出:18(10 + 8)
  • outer_func 调用后,变量 outer_var=10 本应随函数结束而销毁,但闭包让 inner_func 记住了它的值。

  • 每次调用 closure(inner_var),都会用 “记住的 10” 和传入的 inner_var 计算。

3.闭包的核心作用(保存状态)

闭包最大的价值就是保存外部变量的状态,避免使用全局变量

比如实现一个"计数器":

def make_counter():
    count = 0  # 外部变量:记录计数
    
    def counter():
        nonlocal count  # 声明引用外部变量(非全局)
        count += 1
        return count
    
    return counter


# 创建两个计数器(彼此独立,状态不共享)
counter1 = make_counter()
counter2 = make_counter()

print(counter1())  # 1(counter1的count变为1)
print(counter1())  # 2(counter1的count变为2)
print(counter2())  # 1(counter2的count独立变为1)
  • 两个计数器 counter1 和 counter2 分别 “记住” 自己的 count 状态,互不干扰,比用全局变量更优雅。

4. 闭包的条件

形成闭包必须满足 3 个条件:

  1. 存在嵌套函数(内部函数定义在外部函数中);
  2. 内部函数引用了外部函数的非全局变量;
  3. 外部函数返回了内部函数。

 

二、装饰器:基于闭包的 “功能增强” 神器

1. 什么是装饰器?

装饰器本质是一个闭包函数,它的特殊之处在于:

  • 接收一个函数作为参数(被装饰的函数);
  • 返回一个新的函数(添加了额外功能的 “包装函数”);
  • 用 @装饰器名 的语法糖简化调用。

核心作用:在不修改原函数代码的前提下,为其添加额外功能(如日志、计时、权限校验等)。

2. 装饰器的基本用法:给函数 “加功能”

比如,我们想给一个函数添加 “执行前打印日志” 的功能,用装饰器实现如下:

# 1. 定义装饰器(本质是闭包)
def log_decorator(func):  # 接收被装饰的函数作为参数
    def wrapper():  # 包装函数:添加额外功能 + 调用原函数
        print(f"==== 开始执行 {func.__name__} 函数 ====")  # 额外功能:打印日志
        func()  # 调用原函数
        print(f"==== {func.__name__} 函数执行结束 ====")  # 额外功能:打印日志
    return wrapper  # 返回包装函数


# 2. 用 @ 语法糖给原函数“戴装饰器”
@log_decorator
def say_hello():
    print("Hello, 装饰器!")


# 3. 调用原函数(实际执行的是被装饰后的 wrapper 函数)
say_hello()

输出结果:

==== 开始执行 say_hello 函数 ====
Hello, 装饰器!
==== say_hello 函数执行结束 ====
  • 原函数 say_hello 代码未变,但被 log_decorator 装饰后,自动拥有了日志功能;

  • @log_decorator 等价于 say_hello = log_decorator(say_hello),这是 Python 的语法糖简化。

3. 装饰器的工作原理

用一张图看懂装饰器的执行流程:

原函数 say_hello → 被装饰器 log_decorator 接收 → 包装成 wrapper 函数(含日志+原函数调用)→ 变量 say_hello 指向 wrapper

当调用 say_hello() 时,实际执行的是 wrapper(),从而在不修改原函数的情况下 “增强” 了功能。

4. 进阶:装饰带参数的函数

如果原函数有参数(如 add(a, b)),装饰器需要兼容参数传递。只需让 wrapper 函数接收可变参数 *args 和 **kwargs 即可:

def log_decorator(func):
    def wrapper(*args, **kwargs):  # 用 *args 接收位置参数,** kwargs 接收关键字参数
        print(f"==== 执行 {func.__name__},参数:{args}, {kwargs} ====")
        result = func(*args, **kwargs)  # 传递参数给原函数
        print(f"==== {func.__name__} 返回结果:{result} ====")
        return result  # 返回原函数的结果
    return wrapper


@log_decorator
def add(a, b):
    return a + b


# 调用带参数的函数
print(add(3, 5))  # 输出:8

 输出结果:

==== 执行 add,参数:(3, 5), {} ====
==== add 返回结果:8 ====
8
  • *args 和 **kwargs 让装饰器兼容任意参数的函数,通用性极强。

5. 带参数的装饰器:让装饰器更灵活

有时装饰器需要自己的参数(如日志的级别),这时需要三层嵌套的闭包:最外层接收装饰器参数,中间层接收原函数,最内层是包装函数。

例如,实现一个可指定日志级别的装饰器:

# 带参数的装饰器(三层嵌套)
def log_level(level):  # 最外层:接收装饰器参数(日志级别)
    def log_decorator(func):  # 中间层:接收原函数
        def wrapper(*args, **kwargs):  # 最内层:包装函数
            print(f"[{level}] 执行 {func.__name__},参数:{args}")
            result = func(*args, **kwargs)
            return result
        return wrapper
    return log_decorator


# 用不同参数装饰函数
@log_level("INFO")  # 等价于 @log_decorator(log_decorator 由 log_level("INFO") 返回)
def multiply(a, b):
    return a * b

@log_level("WARNING")
def divide(a, b):
    return a / b


print(multiply(2, 3))  # 输出:6
print(divide(6, 2))    # 输出:3.0

输出结果:

[INFO] 执行 multiply,参数:(2, 3)
6
[WARNING] 执行 divide,参数:(6, 2)
3.0
  • 带参数的装饰器本质是 “装饰器工厂”:先调用 log_level("INFO") 生成一个装饰器,再用这个装饰器装饰函数。

6. 修复原函数的 “身份信息”

装饰器会让原函数的元信息(如 __name____doc__)被替换成 wrapper 的信息,例如:

 

def decorator(func):
    def wrapper():
        func()
    return wrapper

@decorator
def test():
    """这是test函数的文档字符串"""
    pass

print(test.__name__)  # 输出:wrapper(本该是 test)
print(test.__doc__)   # 输出:None(本该是“这是test函数的文档字符串”)

解决方法:用 functools.wraps 保留原函数信息:

import functools

def decorator(func):
    @functools.wraps(func)  # 修复元信息
    def wrapper():
        func()
    return wrapper

@decorator
def test():
    """这是test函数的文档字符串"""
    pass

print(test.__name__)  # 输出:test(正确)
print(test.__doc__)   # 输出:这是test函数的文档字符串(正确)
  • 开发装饰器时,建议总是用 functools.wraps 修复元信息,避免调试时 confusion。

三、闭包与装饰器的关系:装饰器是闭包的 “特殊应用”

  • 闭包的核心是 “内部函数记住外部变量”;
  • 装饰器是闭包的一种特殊形式:外部函数接收的变量是 “被装饰的函数”,内部函数(wrapper)调用该函数并添加功能。

可以说,装饰器是为函数增强功能而设计的闭包

四、实际应用场景:装饰器的 “超能力”

装饰器在实际开发中无处不在,以下是几个典型场景:

1. 日志记录

自动记录函数的调用时间、参数、返回值,方便调试和监控:

import time
import functools

def log_time(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"{func.__name__} 执行耗时:{end - start:.2f}秒")
        return result
    return wrapper

@log_time
def slow_func():
    time.sleep(1)  # 模拟耗时操作

slow_func()  # 输出:slow_func 执行耗时:1.00秒

2. 权限验证

在函数执行前检查用户权限,无权限则拒绝执行:

def check_permission(func):
    @functools.wraps(func)
    def wrapper(user):
        if user == "admin":
            return func(user)
        else:
            return "权限不足,无法执行"
    return wrapper

@check_permission
def delete_data(user):
    return f"{user} 成功删除数据"

print(delete_data("admin"))   # 输出:admin 成功删除数据
print(delete_data("guest"))   # 输出:权限不足,无法执行

3. 缓存结果(减少重复计算)

对耗时的函数(如计算斐波那契数列),缓存已计算的结果,避免重复运算:

def cache(func):
    cache_dict = {}  # 闭包变量:缓存计算结果
    @functools.wraps(func)
    def wrapper(n):
        if n not in cache_dict:
            cache_dict[n] = func(n)  # 未缓存则计算并保存
        return cache_dict[n]  # 直接返回缓存结果
    return wrapper

@cache
def fib(n):
    if n <= 1:
        return n
    return fib(n-1) + fib(n-2)

print(fib(100))  # 用了缓存,瞬间出结果(否则会很慢)

总结

-** 闭包 **:让函数 “记住” 外部变量,是实现状态保存的利器(如计数器、缓存);
-** 装饰器 **:基于闭包,专注于 “不修改原函数” 地增强功能(如日志、权限、缓存);

  • 核心思想:利用函数的嵌套和变量作用域,实现代码的 “模块化” 和 “复用”。

掌握闭包和装饰器,能让你的 Python 代码更具 “Pythonic” 风格 —— 简洁、灵活、可扩展。从今天起,试着用装饰器优化你的函数吧!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值