Python高阶精讲之闭包与装饰器彻底吃透(零基础看懂+实战源码)
作者:我材不敲代码
前言
很多Python开发者学了很久,依然看不懂装饰器、不会手写装饰器、不懂闭包原理。
但闭包和装饰器是Python进阶分水岭:源码、框架(Flask/Django)、缓存、日志、接口鉴权、性能统计,底层全部依赖这套语法。
本文用最通俗的语言+分步拆解+完整实战源码,从零带你吃透:函数进阶 → 闭包原理 → 装饰器基础 → 有参装饰器 → 多层装饰器 → 企业级实战场景,零基础也能一次性学懂。
01 前置基础:函数是一等公民
Python中函数是一等对象(一等公民),拥有三个核心特性,这是闭包、装饰器能实现的根本:
- 函数可以赋值给变量
- 函数可以作为参数、返回值
- 函数可以嵌套定义(内部函数)
基础示例
# 1. 函数赋值给变量
def hello():
print("Hello Python")
f = hello
f()
# 2. 函数作为返回值
def outer():
def inner():
print("内部函数执行")
return inner
res = outer()
res()
核心总结:正因为函数可以嵌套、可以返回,才诞生了闭包。
02 闭包详解:定义、条件、原理
2.1 闭包的必备三要素
同时满足以下3点,就是闭包(Closure):
- 函数嵌套(外层函数+内层函数)
- 内层函数引用外层函数的局部变量
- 外层函数返回内层函数
2.2 闭包实战代码
def outer(num):
# 外层局部变量
def inner():
# 内层引用外层变量
print(f"传入数值:{num}")
return inner
# 接收内层函数
func = outer(100)
# 调用内层函数
func()
2.3 闭包核心特性(重点)
正常函数执行结束后,局部变量会被销毁释放。
但闭包会保留外层函数的局部变量,不会被GC回收,实现了数据常驻、状态保存。
2.4 闭包经典场景:计数器
def count_factory():
cnt = 0
def counter():
nonlocal cnt
cnt += 1
print(f"当前计数:{cnt}")
return counter
c1 = count_factory()
c1() # 1
c1() # 2
c1() # 3
知识点:nonlocal 关键字,用于内层函数修改外层嵌套函数的局部变量。
03 装饰器本质:基于闭包的语法糖
3.1 核心认知
装饰器 = 高阶闭包 + 语法糖
装饰器的核心作用:在不修改原函数代码、不修改原函数调用方式的前提下,给函数新增功能。
这就是设计模式中的开闭原则:对扩展开放,对修改关闭。
3.2 装饰器解决的问题
- 避免重复代码(日志、计时、鉴权通用逻辑复用)
- 业务代码与通用工具代码解耦
- 统一项目规范,便于维护迭代
04 手写无参装饰器:日志统计实战
需求:给任意函数添加执行日志、执行耗时统计
4.1 原生闭包写法(理解底层)
import time
def timer_decorator(func):
# 通用装饰器,接收任意参数
def wrapper(*args, **kwargs):
start = time.time()
# 执行原函数
res = func(*args, **kwargs)
end = time.time()
print(f"函数【{func.__name__}】执行耗时:{end - start:.4f}s")
# 返回原函数结果
return res
return wrapper
# 测试函数
@timer_decorator
def calc_sum(n):
s = 0
for i in range(n):
s += i
return s
calc_sum(100000)
4.2 装饰器执行流程拆解
@timer_decorator装饰器加载,等价于calc_sum = timer_decorator(calc_sum)- 调用
calc_sum()实际执行的是内部包装函数wrapper() - wrapper执行前置逻辑 → 执行原函数 → 后置逻辑 → 返回结果
4.3 解决函数信息覆盖问题
直接使用装饰器会导致原函数的函数名、文档注释被wrapper覆盖,需要 functools.wraps 修复:
import time
from functools import wraps
def timer_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
res = func(*args, **kwargs)
end = time.time()
print(f"函数【{func.__name__}】执行耗时:{end - start:.4f}s")
return res
return wrapper
05 进阶:带参数装饰器 + 多层装饰器
5.1 带参数装饰器
普通装饰器只能接收函数参数,带参装饰器可以自定义配置参数,灵活性更高。
需求:自定义日志等级,控制是否打印日志
from functools import wraps
def log_decorator(switch=True):
# 外层接收装饰器参数
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
if switch:
print(f"【日志】函数{func.__name__}开始执行")
res = func(*args, **kwargs)
return res
return wrapper
return decorator
# 开启日志
@log_decorator(switch=True)
def test_func():
print("业务逻辑执行")
test_func()
5.2 多层装饰器叠加执行顺序
加载顺序:自下而上,执行顺序:自上而下
口诀:先装饰的后执行,后装饰的先执行
from functools import wraps
def deco1(func):
@wraps(func)
def wrapper():
print("deco1 前置")
func()
print("deco1 后置")
return wrapper
def deco2(func):
@wraps(func)
def wrapper():
print("deco2 前置")
func()
print("deco2 后置")
return wrapper
@deco1
@deco2
def hello():
print("核心业务执行")
hello()
06 企业级实战三大装饰器(可直接复用)
6.1 接口鉴权装饰器
from functools import wraps
def login_auth(func):
@wraps(func)
def wrapper(user_token, *args, **kwargs):
# 模拟鉴权逻辑
if not user_token:
return {"code": 401, "msg": "未登录,权限不足"}
return func(user_token, *args, **kwargs)
return wrapper
# 模拟接口
@login_auth
def get_user_info(token):
return {"code": 200, "data": "用户信息数据"}
print(get_user_info(""))
6.2 简易缓存装饰器(避免重复计算)
from functools import wraps
def cache_decorator(func):
cache = {}
@wraps(func)
def wrapper(*args):
if args not in cache:
cache[args] = func(*args)
return cache[args]
return wrapper
@cache_decorator
def fib(n):
if n <= 2:
return 1
return fib(n-1) + fib(n-2)
print(fib(30)) # 首次计算,后续直接读取缓存
6.3 异常捕获装饰器(统一异常处理)
from functools import wraps
def exception_catch(func):
@wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception as e:
print(f"【函数异常】{func.__name__}执行失败:{str(e)}")
return None
return wrapper
@exception_catch
def divide(a, b):
return a / b
divide(10, 0)
07 面试高频真题(必背)
Q1:什么是闭包?闭包的作用和缺点?
答:
闭包是嵌套函数中,内层函数引用外层局部变量、外层返回内层函数的代码结构。
作用:保存函数执行状态、实现数据常驻、简化代码、实现装饰器。
缺点:外层变量常驻内存无法释放,长期使用会造成内存泄漏。
Q2:装饰器的原理是什么?为什么不修改原代码?
装饰器基于闭包+高阶函数实现,通过包装原函数,在函数执行前后扩展逻辑,符合开闭原则,实现业务与通用逻辑解耦。
Q3:wraps装饰器的作用?
修复装饰器导致的原函数信息丢失问题,保留原函数的 __name__、文档注释等属性。
Q4:多层装饰器的执行顺序?
加载自下而上,执行自上而下,先装饰的函数最后执行后置逻辑。
到此这篇关于Python高阶精讲之闭包与装饰器彻底吃透(零基础看懂+实战源码)的文章就介绍到这了,更多相关Python闭包与装饰器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
