当前位置: 首页 > news >正文

lesson18:Python函数的闭包与装饰器(难)

目录

引言

闭包:函数式编程的"状态容器"

一、闭包的本质与定义

二、闭包的三大形成条件

三、闭包的工作原理:变量的“持久化”

四、闭包的核心应用场景

五、闭包的注意事项

六、闭包与装饰器的关系

装饰器:基于闭包的功能增强工具

一. 装饰器的定义与作用

二. 装饰器的实现原理(基于闭包)

三、装饰器进阶:灵活扩展功能

1. 带参数的装饰器

2. 保留函数元信息

3. 类装饰器与装饰器嵌套

四、装饰器实战案例

案例一:时间开销计算(性能监控)

案例二:权限校验(安全控制)

案例三:日志记录(行为追踪)

五、注意事项与最佳实践

 总结


引言

在Python中,闭包(Closure)和装饰器(Decorator)是函数式编程的重要特性,它们不仅能让代码更简洁、模块化,还能在不修改原函数代码的前提下为其动态添加功能。本文将从闭包的底层原理出发,逐步深入装饰器的实现机制,并通过三个实战案例(时间开销计算、权限校验、日志记录)展示装饰器的强大应用。


闭包:函数式编程的"状态容器"

一、闭包的本质与定义

闭包(Closure)是Python函数式编程的重要特性,其本质是一个嵌套函数结构,其中内部函数引用了外部函数的非全局变量,且外部函数返回内部函数对象。这种结构使得内部函数可以“记住”其创建时的环境(即外部函数的变量状态),即使外部函数已经执行完毕。

二、闭包的三大形成条件

  1. 嵌套函数结构:存在外层函数(outer)和内层函数(inner),内层函数定义在外部函数内部。
  2. 变量引用:内层函数引用了外层函数中定义的非全局变量(即外层函数的局部变量或参数)。
  3. 返回内层函数:外层函数的返回值是内层函数本身(而非函数调用结果)。

示例:验证闭包的形成

def outer_func(x):
def inner_func(y):
return x + y # 引用外层函数变量x
return inner_func # 返回内层函数# 创建闭包实例
closure = outer_func(5)
print(closure(3)) # 输出:8(x=5被“记住”,与y=3相加)

三、闭包的工作原理:变量的“持久化”

  • 正常情况下:函数执行完毕后,其局部变量会被Python垃圾回收机制销毁。
  • 闭包中:由于内层函数引用了外层函数的变量,这些变量会被“保留”在内存中,供内层函数后续调用时使用。这种特性称为变量的持久化

底层逻辑
闭包通过__closure__属性存储被引用的变量(以元组形式),每个元素是cell对象,通过cell_contents可访问变量值:

print(closure.__closure__) # 输出:(<cell at 0x...: int object at 0x...>,)
print(closure.__closure__[0].cell_contents) # 输出:5(外层函数的x值)

四、闭包的核心应用场景

  1. 数据私有化与封装
    闭包可隐藏内部状态,仅通过返回的内层函数暴露接口,实现类似“类私有变量”的效果。

    def counter():
    count = 0 # 私有变量,外部无法直接访问
    def increment():
    nonlocal count # 声明为非局部变量(允许修改外层变量)
    count += 1
    return count
    return incrementc = counter()
    print(c()) # 1
    print(c()) # 2(count状态被持续保留)
  2. 延迟计算(Lazy Evaluation)
    将计算逻辑封装在闭包中,按需触发执行,避免不必要的资源消耗。

    def lazy_calc(func):
    def wrapper(*args):
    result = None
    def calc():
    nonlocal result
    if result is None:
    result = func(*args) # 首次调用时计算
    return result
    return calc
    return wrapper@lazy_calc
    def expensive_computation(n):
    print("计算中...")
    return sum(range(n))calc = expensive_computation(1000000)
    print(calc()) # 计算中... 499999500000(首次执行)
    print(calc()) # 499999500000(直接返回缓存结果)
  3. 装饰器的基础
    装饰器本质是闭包的高级应用,通过嵌套函数接收原函数、添加功能并返回新函数(详见之前装饰器案例)。

五、闭包的注意事项

  1. 变量修改需用nonlocal:若内层函数需修改外层变量,需用nonlocal声明(否则视为局部变量)。
  2. 避免引用可变对象:若外层变量是列表、字典等可变对象,修改时无需nonlocal,但需注意副作用。
def outer():
lst = []
def inner(x):
lst.append(x) # 可变对象修改无需nonlocal
return lst
return inneradd = outer()
print(add(1)) # [1]
print(add(2)) # [1, 2]
  1. 内存占用问题:闭包会“记住”所有引用的变量,若滥用可能导致内存泄漏,需谨慎使用。

六、闭包与装饰器的关系

闭包是装饰器的底层实现基础。装饰器通过闭包接收原函数作为参数,在内部函数中包裹原函数并添加额外功能(如日志、计时),最终返回增强后的函数。理解闭包是掌握装饰器的关键前提。


装饰器:基于闭包的功能增强工具

一. 装饰器的定义与作用

装饰器是一种接收函数作为参数,并返回新函数的工具,用于在不修改原函数代码的情况下为其添加额外功能(如日志、缓存、权限校验等)。其语法通过@装饰器名实现,简洁优雅。

二. 装饰器的实现原理(基于闭包)

装饰器的核心逻辑是闭包嵌套:外部函数接收原函数作为参数,内部函数包裹原函数并添加新功能,最后返回内部函数。

示例:简单装饰器(打印函数调用信息)

def log_decorator(func):
def wrapper(*args, **kwargs):
print(f"调用函数:{func.__name__}")
result = func(*args, **kwargs) # 执行原函数
print(f"函数返回:{result}")
return result
return wrapper# 使用装饰器
@log_decorator
def add(a, b):
return a + badd(2, 3)
# 输出:
# 调用函数:add
# 函数返回:5

三、装饰器进阶:灵活扩展功能

1. 带参数的装饰器

若需为装饰器传递参数(如日志级别、权限角色),需在基础装饰器外再嵌套一层函数,用于接收参数。

示例:带参数的日志装饰器

def log_level(level):
def decorator(func):
def wrapper(*args, **kwargs):
print(f"[{level}] 调用函数:{func.__name__}")
return func(*args, **kwargs)
return wrapper
return decorator@log_level("INFO") # 传递参数
def greet(name):
return f"Hello, {name}!"greet("Alice") # 输出:[INFO] 调用函数:greet
2. 保留函数元信息

默认情况下,装饰后的函数会丢失原函数的元信息(如__name____doc__)。需使用functools.wraps装饰器修复这一问题:

from functools import wrapsdef log_decorator(func):
@wraps(func) # 保留原函数元信息
def wrapper(*args, **kwargs):
print(f"调用函数:{func.__name__}")
return func(*args, **kwargs)
return wrapper@log_decorator
def add(a, b):
"""两数相加"""
return a + bprint(add.__name__) # 输出:add(未用wraps时输出wrapper)
print(add.__doc__) # 输出:两数相加
3. 类装饰器与装饰器嵌套
  • 类装饰器:通过__call__方法实现,适用于复杂逻辑(如维护状态);
  • 装饰器嵌套:多个装饰器可叠加使用,执行顺序为从下到上(靠近函数的装饰器先执行)。

四、装饰器实战案例

案例一:时间开销计算(编写装饰器统计使用冒泡排序与选择排序时间开销)

需求:测量函数执行时间,用于性能优化。

import random
import timedef time_cost(fun):def calc(n):start = time.time()r = fun(n)end = time.time()print(f"方法{fun}耗时{end - start}")return rreturn calc@time_cost
def bub(datas):for i in range(len(datas) - 1):for j in range(len(datas) - 1 - i):if datas[j] > datas[j + 1]:datas[j], datas[j + 1] = datas[j + 1], datas[j]return datas@time_cost
def choice(datas):for i in range(len(datas)):max_value = ifor j in range(i + 1, max_value):if datas[j] > max_value:max_value = jdatas[i], datas[max_value] = datas[max_value], datas[i]return datasdatas = [random.randint(1, 1000) for i in range(1000)]
print(bub(datas))
print(choice(datas))
案例二:权限校验(安全控制)

需求:仅允许指定角色(如管理员)执行函数。

from functools import wrapsuser = Nonedef login_required(f):@wraps(f)def check():global userif user:f()else:username = input("请输入用户名")password = input("请输入密码")if username == "admin" and password == "123456":user = usernamef()else:print("登录失败")return check@login_required
def home():print("首页")
home()@login_required
def person():print("个人中心")
person()
案例三:日志记录(行为追踪)

需求:将函数调用信息(时间、参数、返回值)写入日志文件。

from datetime import datetimedef exec_log(f):def fun(*args):r = f(*args)with open("record.gh", "a") as file:msg = (f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"f"{f.__name__}"f"参数列表:{args}返回值:{r}")file.write(msg)return rreturn fun@exec_log
def add(a, b):return a + b
print(add(4, 9))@exec_log
def get_sum(n):total = 0for i in range(n + 1):total += ireturn total
print(get_sum(9))

五、注意事项与最佳实践

  1. 装饰器执行顺序:多个装饰器时,靠近函数的先执行(如@d1 @d2 def f(),先执行d2再执行d1);
  2. 避免过度使用:滥用装饰器会增加代码复杂度,建议仅用于通用功能(如日志、权限);
  3. 调试难度:装饰器可能隐藏函数调用栈,可使用functools.wrapsinspect模块辅助调试。

 总结

闭包是装饰器的基础,通过封装状态实现函数"记忆";装饰器则基于闭包,提供了一种优雅的方式为函数动态添加功能。本文从原理到实战,详细讲解了闭包的定义、装饰器的实现与进阶用法,并通过三个案例展示了装饰器在性能监控、安全控制和日志追踪中的应用。掌握这些工具,能显著提升代码的复用性和可维护性,是Python开发者进阶的必备技能。

http://www.dtcms.com/a/287818.html

相关文章:

  • TypeScript 泛型详解:从基础到实战应用
  • 3.条件判断:让程序学会做选择
  • Web开发 03
  • import.meta.glob 与 import.meta.env、import的几个概念的简单回顾
  • react+antd+表格拖拽排序以及上移、下移、移到顶部、移到底部
  • 408数据结构强化(自用)
  • 实现el-select下拉框,下拉时加载数据
  • MYSQL 第一次作业
  • 《命令行参数与环境变量:从使用到原理的全方位解析》
  • Flink实时流量统计:基于窗口函数与Redis Sink的每小时PV监控系统(学习记录)
  • UniApp 自定义导航栏:解决安全区域适配问题的完整实践
  • C++基于muduo库从零实现Rpc框架
  • Ubuntu18.04环境下,vscode使用clangd、bear实时准确跳转过程中遇到的compile_commands.json无法解析问题
  • windows wsl ubuntu 如何安装 maven
  • 程序混淆的可行性?
  • PyCharm 入门指南:起步学习、开发环境一体
  • java: DDD using sql server 2019 or Oracle21c
  • WLAN Autoconfig 自启动失效/WIFI功能消失问题解决方案
  • Gradle安装教程
  • 深入理解设计模式之模板模式:优雅地定义算法骨架
  • 在RK3588开发板快速搭建ros环境以及运行ros程序(以usb_cam为例)
  • 云服务器搭建自己的FRP服务。为什么客户端的项目需要用Docker启动,服务端才能够访问到?
  • 详细解读Go中的 fmt包
  • 2025年医疗人工智能发展现状
  • JMeter 元件使用详解
  • 初学者STM32—DMA数据转运
  • [DBC教程 一] DBC文件概述及搭建编辑环境CANdb++ Admin
  • [LGR-233-Div.4]洛谷入门赛#37——8道题题解
  • CF 训练 2 D - E参考代码
  • 面试高频题 力扣 130. 被围绕的区域 洪水灌溉(FloodFill) 深度优先遍历(dfs) 暴力搜索 C++解题思路 每日一题