Python装饰器和闭包

学习时长:3小时 难度:中级 练习:4个

闭包基础

什么是闭包?

闭包是一个函数对象,它可以访问其定义所在作用域中的变量,即使该作用域已经结束。闭包让你可以在一个内部函数中访问外部函数的作用域。

闭包示例

def make_counter(start=0):
    """创建一个计数器闭包"""
    count = [start]  # 使用列表存储状态
    
    def counter():
        count[0] += 1
        return count[0]
    
    return counter

# 使用闭包
counter1 = make_counter(5)
print(counter1())  # 输出: 6
print(counter1())  # 输出: 7

counter2 = make_counter(10)
print(counter2())  # 输出: 11
print(counter1())  # 输出: 8
                    
注意事项

Python3中的闭包可以使用nonlocal关键字修改外部函数的变量,但要谨慎使用,以免造成代码难以理解。

装饰器基础

什么是装饰器?

装饰器是一个可调用对象,它可以包装另一个函数或类,并且可以在不修改原始函数代码的情况下扩展其功能。


# 基本装饰器
def log_calls(func):
    def wrapper(*args, **kwargs):
        print(f"调用函数: {func.__name__}")
        result = func(*args, **kwargs)
        print(f"函数 {func.__name__} 返回: {result}")
        return result
    return wrapper

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

# 使用装饰器
result = add(3, 5)  # 自动打印日志
                
装饰器工作原理

使用@语法糖等同于:


def add(a, b):
    return a + b
add = log_calls(add)  # 手动装饰
                    

装饰器参数

带参数的装饰器

def repeat(times=1):
    """创建一个可以指定重复次数的装饰器"""
    def decorator(func):
        def wrapper(*args, **kwargs):
            for _ in range(times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(times=3)
def greet(name):
    print(f"你好, {name}!")

# 调用函数
greet("小明")  # 会打印三次问候
                    
保留函数元信息

from functools import wraps

def log_calls(func):
    @wraps(func)  # 保留原函数的元信息
    def wrapper(*args, **kwargs):
        print(f"调用函数: {func.__name__}")
        return func(*args, **kwargs)
    return wrapper
                    

类装饰器

装饰类的方法

class Cache:
    def __init__(self):
        self._cache = {}
    
    def __call__(self, func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            key = str(args) + str(kwargs)
            if key not in self._cache:
                self._cache[key] = func(*args, **kwargs)
            return self._cache[key]
        return wrapper

# 使用类装饰器
@Cache()
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)
                    
装饰整个类

def singleton(cls):
    """实现单例模式的装饰器"""
    instances = {}
    
    def get_instance(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    
    return get_instance

@singleton
class Database:
    def __init__(self):
        print("初始化数据库连接...")
                    

实际应用

1. 性能监控装饰器

import time
import functools

def measure_time(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"{func.__name__} 执行时间: {end_time - start_time:.4f} 秒")
        return result
    return wrapper

@measure_time
def process_data(data):
    time.sleep(1)  # 模拟耗时操作
    return len(data)
                    
2. 缓存装饰器

def memoize(func):
    cache = {}
    
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        key = str(args) + str(kwargs)
        if key not in cache:
            cache[key] = func(*args, **kwargs)
        return cache[key]
    return wrapper

@memoize
def compute_expensive(n):
    print(f"计算 {n}")
    return sum(i * i for i in range(n))
                    
3. 权限验证装饰器

def require_permission(permission):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(user, *args, **kwargs):
            if permission in user.permissions:
                return func(user, *args, **kwargs)
            else:
                raise PermissionError(f"用户没有{permission}权限")
        return wrapper
    return decorator

@require_permission("admin")
def delete_user(current_user, user_id):
    print(f"删除用户 {user_id}")
                    
4. 参数验证装饰器

def validate_types(**expected_types):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            # 检查参数类型
            for arg_name, expected_type in expected_types.items():
                if arg_name in kwargs:
                    if not isinstance(kwargs[arg_name], expected_type):
                        raise TypeError(f"{arg_name} 必须是 {expected_type}")
            return func(*args, **kwargs)
        return wrapper
    return decorator

@validate_types(age=int, name=str)
def register_user(name, age):
    print(f"注册用户: {name}, 年龄: {age}")
                    

练习与实践

练习1:重试装饰器

实现一个装饰器,在函数失败时自动重试指定次数。


def retry(max_attempts=3, delay=1):
    """
    装饰器:在函数失败时自动重试
    max_attempts: 最大重试次数
    delay: 重试间隔(秒)
    """
    # 在此编写你的代码
    pass
                    

练习2:日志装饰器

创建一个装饰器,记录函数的调用信息到文件。


def log_to_file(filename):
    """
    装饰器:记录函数调用到文件
    filename: 日志文件名
    """
    # 在此编写你的代码
    pass
                    

练习3:缓存装饰器

实现一个带有过期时间的缓存装饰器。


def cache_with_timeout(timeout_seconds):
    """
    装饰器:缓存函数结果,并在指定时间后过期
    timeout_seconds: 缓存过期时间(秒)
    """
    # 在此编写你的代码
    pass