掌握Python装饰器:让你的代码更优雅更强大

在Python编程的世界里,装饰器(Decorator)无疑是最优雅且强大的特性之一。它不仅能让你的代码更加简洁易读,还能大幅提升代码的复用性和可维护性。无论你是Python初学者还是经验丰富的开发者,掌握装饰器都将让你的编程技能更上一层楼。

目录

  1. 什么是装饰器?
  2. 装饰器的工作原理
  3. 基础装饰器实战
  4. 带参数的装饰器
  5. 类装饰器的魅力
  6. 装饰器的高级应用
  7. 装饰器最佳实践
  8. 常见陷阱与解决方案
  9. 总结

什么是装饰器?

装饰器是Python中一种特殊的语法糖,它允许我们在不修改原函数代码的情况下,为函数添加额外的功能。简单来说,装饰器就是一个接受函数作为参数并返回新函数的函数

想象一下,你有一个房子(原函数),装饰器就像是为房子添加装修(新功能),而不需要拆掉重建。

# 最简单的装饰器示例
def my_decorator(func):
    def wrapper():
        print("装饰器开始工作")
        func()
        print("装饰器工作结束")
    return wrapper

@my_decorator
def say_hello():
    print("Hello, World!")

# 调用函数
say_hello()

输出:

装饰器开始工作
Hello, World!
装饰器工作结束

装饰器的工作原理

要深入理解装饰器,我们需要明白Python中的几个核心概念:

1. 函数是一等公民

在Python中,函数可以像变量一样被传递、赋值和返回:

def greet():
    return "Hello!"

# 函数可以赋值给变量
my_func = greet
print(my_func())  # 输出: Hello!

# 函数可以作为参数传递
def call_function(func):
    return func()

print(call_function(greet))  # 输出: Hello!

2. 闭包的概念

装饰器的核心机制依赖于闭包:

def outer_function(x):
    def inner_function(y):
        return x + y  # inner_function可以访问outer_function的变量
    return inner_function

add_10 = outer_function(10)
print(add_10(5))  # 输出: 15

3. 装饰器的等价写法

使用@语法糖:

@my_decorator
def my_function():
    pass

等价于:

def my_function():
    pass
my_function = my_decorator(my_function)

基础装饰器实战

让我们通过几个实用的例子来掌握装饰器的基本用法:

1. 执行时间统计装饰器

import time
import functools

def timing_decorator(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

@timing_decorator
def slow_function():
    time.sleep(1)
    return "任务完成"

result = slow_function()
print(result)

2. 日志记录装饰器

import logging
from datetime import datetime

# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(message)s')

def log_calls(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        logging.info(f"调用函数 {func.__name__}")
        logging.info(f"参数: args={args}, kwargs={kwargs}")
        
        try:
            result = func(*args, **kwargs)
            logging.info(f"函数 {func.__name__} 执行成功")
            return result
        except Exception as e:
            logging.error(f"函数 {func.__name__} 执行失败: {e}")
            raise
    return wrapper

@log_calls
def divide(a, b):
    return a / b

# 测试
print(divide(10, 2))  # 正常执行
print(divide(10, 0))  # 会抛出异常

3. 重试机制装饰器

import random
import time

def retry(max_attempts=3, delay=1):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(max_attempts):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    if attempt == max_attempts - 1:
                        print(f"函数 {func.__name__}{max_attempts} 次尝试后仍然失败")
                        raise e
                    print(f"第 {attempt + 1} 次尝试失败,{delay}秒后重试...")
                    time.sleep(delay)
        return wrapper
    return decorator

@retry(max_attempts=3, delay=0.5)
def unreliable_network_call():
    if random.random() < 0.7:  # 70%的概率失败
        raise ConnectionError("网络连接失败")
    return "数据获取成功"

# 测试重试机制
try:
    result = unreliable_network_call()
    print(result)
except ConnectionError as e:
    print(f"最终失败: {e}")

带参数的装饰器

带参数的装饰器需要三层嵌套函数:

def rate_limit(calls_per_second):
    def decorator(func):
        last_called = [0.0]  # 使用列表来存储可变值
        
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            elapsed = time.time() - last_called[0]
            left_to_wait = 1.0 / calls_per_second - elapsed
            if left_to_wait > 0:
                time.sleep(left_to_wait)
            ret = func(*args, **kwargs)
            last_called[0] = time.time()
            return ret
        return wrapper
    return decorator

@rate_limit(2)  # 每秒最多调用2次
def api_call():
    print(f"API调用时间: {datetime.now().strftime('%H:%M:%S.%f')[:-3]}")

# 测试限流效果
for i in range(5):
    api_call()

类装饰器的魅力

1. 单例模式装饰器

def singleton(cls):
    instances = {}
    @functools.wraps(cls)
    def get_instance(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    return get_instance

@singleton
class DatabaseConnection:
    def __init__(self):
        print("创建数据库连接")
        self.connection_id = id(self)
    
    def query(self, sql):
        return f"执行查询: {sql}"

# 测试单例模式
db1 = DatabaseConnection()
db2 = DatabaseConnection()
print(f"db1 ID: {db1.connection_id}")
print(f"db2 ID: {db2.connection_id}")
print(f"是否为同一实例: {db1 is db2}")

2. 属性验证装饰器

def validate_types(**expected_types):
    def decorator(cls):
        original_setattr = cls.__setattr__
        
        def new_setattr(self, name, value):
            if name in expected_types:
                expected_type = expected_types[name]
                if not isinstance(value, expected_type):
                    raise TypeError(f"{name} 必须是 {expected_type.__name__} 类型")
            original_setattr(self, name, value)
        
        cls.__setattr__ = new_setattr
        return cls
    return decorator

@validate_types(name=str, age=int, salary=float)
class Employee:
    def __init__(self, name, age, salary):
        self.name = name
        self.age = age
        self.salary = salary

# 测试类型验证
emp = Employee("张三", 30, 5000.0)
print(f"员工: {emp.name}, 年龄: {emp.age}, 薪资: {emp.salary}")

try:
    emp.age = "三十"  # 这会抛出TypeError
except TypeError as e:
    print(f"类型错误: {e}")

装饰器的高级应用

1. 缓存装饰器

def memoize(func):
    cache = {}
    
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        # 创建缓存键
        key = str(args) + str(sorted(kwargs.items()))
        
        if key in cache:
            print(f"缓存命中: {func.__name__}{args}")
            return cache[key]
        
        print(f"计算中: {func.__name__}{args}")
        result = func(*args, **kwargs)
        cache[key] = result
        return result
    
    # 添加清除缓存的方法
    wrapper.clear_cache = lambda: cache.clear()
    wrapper.cache_info = lambda: f"缓存大小: {len(cache)}"
    
    return wrapper

@memoize
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

# 测试缓存效果
print(f"fibonacci(10) = {fibonacci(10)}")
print(fibonacci.cache_info())

2. 权限控制装饰器

from enum import Enum

class Permission(Enum):
    READ = "read"
    WRITE = "write"
    ADMIN = "admin"

def require_permission(required_permission):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(user, *args, **kwargs):
            if not hasattr(user, 'permissions'):
                raise PermissionError("用户没有权限信息")
            
            if required_permission not in user.permissions:
                raise PermissionError(f"需要 {required_permission.value} 权限")
            
            return func(user, *args, **kwargs)
        return wrapper
    return decorator

class User:
    def __init__(self, name, permissions):
        self.name = name
        self.permissions = permissions

@require_permission(Permission.READ)
def read_data(user, data_id):
    return f"用户 {user.name} 读取数据 {data_id}"

@require_permission(Permission.ADMIN)
def delete_data(user, data_id):
    return f"用户 {user.name} 删除数据 {data_id}"

# 测试权限控制
admin_user = User("管理员", [Permission.READ, Permission.WRITE, Permission.ADMIN])
normal_user = User("普通用户", [Permission.READ])

print(read_data(admin_user, "data_001"))
print(read_data(normal_user, "data_002"))

try:
    delete_data(normal_user, "data_003")  # 这会抛出权限错误
except PermissionError as e:
    print(f"权限错误: {e}")

装饰器最佳实践

1. 始终使用 functools.wraps

# ❌ 错误做法
def bad_decorator(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

# ✅ 正确做法
def good_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

def test_function():
    """这是一个测试函数"""
    pass

# 比较效果
bad_decorated = bad_decorator(test_function)
good_decorated = good_decorator(test_function)

print(f"原函数名: {test_function.__name__}")
print(f"错误装饰后: {bad_decorated.__name__}")  # wrapper
print(f"正确装饰后: {good_decorated.__name__}")  # test_function

2. 装饰器组合的顺序

def bold(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        return f"<b>{result}</b>"
    return wrapper

def italic(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        return f"<i>{result}</i>"
    return wrapper

@bold
@italic
def say_hello():
    return "Hello, World!"

print(say_hello())  # 输出: <b><i>Hello, World!</i></b>

3. 可选参数的装饰器

def smart_decorator(func=None, *, prefix="[LOG]"):
    def decorator(f):
        @functools.wraps(f)
        def wrapper(*args, **kwargs):
            print(f"{prefix} 调用 {f.__name__}")
            return f(*args, **kwargs)
        return wrapper
    
    if func is None:
        # 带参数调用: @smart_decorator(prefix="[DEBUG]")
        return decorator
    else:
        # 无参数调用: @smart_decorator
        return decorator(func)

# 两种使用方式
@smart_decorator
def function1():
    return "function1 执行"

@smart_decorator(prefix="[DEBUG]")
def function2():
    return "function2 执行"

function1()  # 输出: [LOG] 调用 function1
function2()  # 输出: [DEBUG] 调用 function2

常见陷阱与解决方案

1. 装饰器中的循环变量问题

# ❌ 错误做法
def create_multipliers():
    multipliers = []
    for i in range(3):
        def multiplier(x):
            return x * i  # 这里的i会是循环结束时的值
        multipliers.append(multiplier)
    return multipliers

# ✅ 正确做法
def create_multipliers_fixed():
    multipliers = []
    for i in range(3):
        def multiplier(x, factor=i):  # 使用默认参数捕获当前值
            return x * factor
        multipliers.append(multiplier)
    return multipliers

# 测试
bad_multipliers = create_multipliers()
good_multipliers = create_multipliers_fixed()

print("错误版本:")
for i, mult in enumerate(bad_multipliers):
    print(f"multiplier {i}: 5 * ? = {mult(5)}")

print("\n正确版本:")
for i, mult in enumerate(good_multipliers):
    print(f"multiplier {i}: 5 * {i} = {mult(5)}")

2. 装饰器的性能考虑

# 对于频繁调用的函数,要注意装饰器的性能开销
def performance_test():
    import time
    
    def simple_decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            return func(*args, **kwargs)
        return wrapper
    
    def heavy_decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            # 模拟重量级操作
            time.sleep(0.001)
            return func(*args, **kwargs)
        return wrapper
    
    def simple_function():
        return 42
    
    @simple_decorator
    def decorated_simple():
        return 42
    
    @heavy_decorator
    def decorated_heavy():
        return 42
    
    # 性能测试
    iterations = 1000
    
    start = time.time()
    for _ in range(iterations):
        simple_function()
    print(f"原函数: {time.time() - start:.4f}秒")
    
    start = time.time()
    for _ in range(iterations):
        decorated_simple()
    print(f"轻量装饰器: {time.time() - start:.4f}秒")
    
    start = time.time()
    for _ in range(iterations):
        decorated_heavy()
    print(f"重量装饰器: {time.time() - start:.4f}秒")

# performance_test()  # 取消注释来运行性能测试

总结

Python装饰器是一个强大而优雅的特性,它能够:

  1. 提升代码复用性 - 将通用功能抽象为装饰器,避免重复代码
  2. 增强代码可读性 - 通过声明式的方式表达函数的额外行为
  3. 保持代码整洁 - 分离核心逻辑和辅助功能
  4. 支持动态行为 - 在运行时为函数添加或修改功能

掌握装饰器不仅能让你写出更优雅的Python代码,还能帮助你更好地理解Python的高级特性。在实际开发中,合理使用装饰器能够显著提升代码质量和开发效率。

记住,优秀的装饰器应该:

  • 功能单一且明确
  • 保留原函数的元信息
  • 处理好参数传递
  • 考虑性能影响
  • 提供清晰的文档

现在就开始在你的项目中使用装饰器,让你的Python代码更加优雅和强大吧!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天天进步2015

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值