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 increment
    
    
    c = 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 inner


add = 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 + b


add(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 wraps


def 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 + b


print(add.__name__) # 输出:add(未用wraps时输出wrapper)
print(add.__doc__) # 输出:两数相加
3. 类装饰器与装饰器嵌套
  • 类装饰器:通过__call__方法实现,适用于复杂逻辑(如维护状态);
  • 装饰器嵌套:多个装饰器可叠加使用,执行顺序为从下到上(靠近函数的装饰器先执行)。

四、装饰器实战案例

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

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

import random
import time

def time_cost(fun):
    def calc(n):
        start = time.time()
        r = fun(n)
        end = time.time()
        print(f"方法{fun}耗时{end - start}")
        return r
    return 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 = i
        for j in range(i + 1, max_value):
            if datas[j] > max_value:
                max_value = j
        datas[i], datas[max_value] = datas[max_value], datas[i]
    return datas


datas = [random.randint(1, 1000) for i in range(1000)]
print(bub(datas))
print(choice(datas))
案例二:权限校验(安全控制)

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

from functools import wraps

user = None

def login_required(f):
    @wraps(f)
    def check():
        global user
        if user:
            f()
        else:
            username = input("请输入用户名")
            password = input("请输入密码")
            if username == "admin" and password == "123456":
                user = username
                f()
            else:
                print("登录失败")
    return check

@login_required
def home():
    print("首页")
home()

@login_required
def person():
    print("个人中心")
person()
案例三:日志记录(行为追踪)

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

from datetime import datetime

def 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 r
    return fun

@exec_log
def add(a, b):
    return a + b
print(add(4, 9))

@exec_log
def get_sum(n):
    total = 0
    for i in range(n + 1):
        total += i
    return total
print(get_sum(9))

五、注意事项与最佳实践

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

 总结

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值