当前位置:首页 > Python > 正文内容

Python装饰器从入门到实战

admin5天前Python12

Python装饰器是Python中一个强大且优雅的特性,它允许我们在不修改原函数代码的情况下,动态地为函数添加额外的功能。本文将从基础概念出发,逐步深入到装饰器的实际应用场景,帮助你全面掌握这一重要技术。

什么是装饰器?

装饰器本质上是一个Python函数,它接受一个函数作为参数,并返回一个新的函数。这个新函数通常会在原函数执行前后添加一些额外的逻辑。装饰器使用@符号语法糖来应用,使代码更加简洁优雅。

从数学角度看,装饰器就像是函数的包装器,它不会改变函数的核心逻辑,而是增强或扩展其功能。这种设计模式在AOP(面向切面编程)中非常常见。

装饰器的基本语法

让我们通过一个简单的例子来理解装饰器的基本用法。假设我们想要在函数执行前后打印一些日志信息:


def log_decorator(func):
    def wrapper(*args, **kwargs):
        print(f"[开始] 执行函数: {func.__name__}")
        result = func(*args, **kwargs)
        print(f"[结束] 函数 {func.__name__} 执行完成,返回值: {result}")
        return result
    return wrapper

@log_decorator
def add_numbers(a, b):
    """计算两个数的和"""
    return a   b

@log_decorator
def multiply_numbers(a, b):
    """计算两个数的积"""
    return a * b

# 测试装饰器
add_numbers(5, 3)
print("---分割线---")
multiply_numbers(4, 6)

运行这段代码,你会看到每次调用被装饰的函数时,都会自动打印开始和结束的日志信息,而不需要修改原函数的代码。

带参数的装饰器

有时候我们需要让装饰器接受参数,这样可以更加灵活地控制装饰器的行为。带参数的装饰器实际上是一个装饰器工厂,它返回真正的装饰器:


def repeat_decorator(times):
    def decorator(func):
        def wrapper(*args, **kwargs):
            results = []
            for _ in range(times):
                result = func(*args, **kwargs)
                results.append(result)
            return results
        return wrapper
    return decorator

@repeat_decorator(3)
def greet(name):
    """问候函数"""
    return f"你好, {name}!"

# 测试带参数的装饰器
print(greet("小豆包"))

这个装饰器让函数执行指定次数,并收集所有结果返回。你可以轻松修改times参数来改变执行次数。

保留原函数的元数据

使用装饰器后,新函数的__name__、__doc__等属性会被wrapper函数覆盖。为了解决这个问题,Python提供了functools.wraps装饰器:


import functools

def smart_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        """包装器函数"""
        print(f"正在执行: {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

@smart_decorator
def my_function():
    """我的原始函数"""
    return "Hello, World!"

print(f"函数名: {my_function.__name__}")
print(f"文档字符串: {my_function.__doc__}")

使用@functools.wraps后,新函数会保留原函数的所有元数据,这对于调试和文档生成非常重要。

类装饰器

除了函数装饰器,Python还支持类装饰器。类装饰器通过实现__call__方法来使类的实例可以像函数一样调用:


class CountCalls:
    def __init__(self, func):
        self.func = func
        self.count = 0
    
    def __call__(self, *args, **kwargs):
        self.count  = 1
        print(f"函数 {self.func.__name__} 已调用 {self.count} 次")
        return self.func(*args, **kwargs)
    
    def get_call_count(self):
        return self.count

@CountCalls
def factorial(n):
    """计算阶乘"""
    if n <= 1:
        return 1
    return n * factorial(n - 1)

# 测试类装饰器
print(factorial(5))
print(factorial(3))
print(f"总调用次数: {factorial.get_call_count()}")

类装饰器的优势在于可以维护状态,比如上面例子中的调用计数器。

装饰器的实际应用场景

1. 性能计时装饰器


import time

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

@timer_decorator
def slow_function():
    """模拟耗时操作"""
    time.sleep(2)
    return "完成"

slow_function()

2. 缓存装饰器


def cache_decorator(func):
    cache = {}
    
    @functools.wraps(func)
    def wrapper(*args):
        if args in cache:
            print(f"从缓存获取: {args}")
            return cache[args]
        result = func(*args)
        cache[args] = result
        return result
    
    def clear_cache():
        cache.clear()
    
    wrapper.clear_cache = clear_cache
    return wrapper

@cache_decorator
def fibonacci(n):
    """斐波那契数列(带缓存)"""
    if n <= 1:
        return n
    return fibonacci(n - 1)   fibonacci(n - 2)

print(f"fibonacci(10) = {fibonacci(10)}")
print(f"fibonacci(10) = {fibonacci(10)}")  # 这次会从缓存获取

3. 权限验证装饰器


def permission_decorator(allowed_roles):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            current_user = get_current_user()
            if current_user[role] not in allowed_roles:
                raise PermissionError("权限不足")
            return func(*args, **kwargs)
        return wrapper
    return decorator

# 模拟用户系统
def get_current_user():
    return {name: test_user, role: guest}

@permission_decorator([admin, editor])
def admin_operation():
    return "管理员操作成功"

try:
    admin_operation()
except PermissionError as e:
    print(f"错误: {e}")

装饰器的叠加使用

Python允许将多个装饰器叠加使用,装饰器会从下往上依次执行:


@timer_decorator
@repeat_decorator(2)
def complex_function(x):
    """复杂函数"""
    time.sleep(0.5)
    return x ** 2

print(complex_function(3))

在这个例子中,complex_function首先被repeat_decorator装饰,然后被timer_decorator装饰。执行时会先计时,然后重复执行原函数。

装饰器的最佳实践

1. 总是使用functools.wraps

确保装饰器不会破坏原函数的元数据,这对于调试和反射操作至关重要。

2. 保持装饰器简单

装饰器应该专注于单一职责,如果一个装饰器做太多事情,考虑将其拆分成多个装饰器。

3. 处理异常

装饰器应该妥善处理异常,或者在异常发生时提供有意义的错误信息:


def safe_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception as e:
            print(f"函数 {func.__name__} 执行出错: {e}")
            raise
    return wrapper

4. 文档化装饰器

为装饰器添加清晰的文档说明,包括其用途、参数和示例。

总结

Python装饰器是一个强大而优雅的特性,它可以帮助我们编写更加简洁、可维护的代码。通过装饰器,我们可以:

  • 在不修改原函数代码的情况下添加额外功能
  • 实现横切关注点的分离(如日志、计时、缓存等)
  • 提高代码的可重用性和可维护性

从简单的日志记录到复杂的权限验证,装饰器在Python开发中有着广泛的应用场景。掌握装饰器将让你的Python代码更加Pythonic,更加优雅。

建议在实际项目中多使用装饰器,逐步体会它的魅力。当你发现一段代码在多个函数中重复出现时,考虑用装饰器来重构它,你会发现代码会变得更加清晰和易于维护。

继续探索Python装饰器的更多可能性,你会发现它远比你想象的更加强大!

相关文章

[Python 教程] OpenCV-Python 入门:图像处理基础详解

OpenCV-Python 入门:图像处理基础详解OpenCV 是一个跨平台计算机视觉库,轻量级且高效,支持 Python 接口。本文将系统介绍 OpenCV 的核心概念和基础操作。一、OpenCV...

[Python 教程] Python 多线程编程指南

Python 多线程编程指南 Python 的 threading 模块提供多线程支持。本文介绍多线程编程的基础和实用技巧。 一、创建线程 import threading import time...

Python 装饰器实战:从基础到高级应用的完整指南

装饰器是 Python 中最优雅也最强大的特性之一。它允许你在不修改原函数代码的前提下,动态地添加功能。本文将带你从装饰器的基础概念出发,逐步掌握其在实际开发中的高级应用技巧。许多初学者对装饰器感到困...

Python 装饰器完全指南:从原理到实战的 5 个核心场景

装饰器(Decorator)是 Python 中最具魅力的特性之一。它允许我们在不修改原函数代码的前提下,动态地添加功能。但很多开发者对装饰器的理解仅停留在在函数上面加个@符号的层面。今天,我们将从底...

Python 装饰器进阶:从入门到实战,写出更灵活的函数增强技巧

# Python 装饰器进阶:从入门到实战,写出更灵活的函数增强技巧 ## 简介 很多 Python 开发者都听过装饰器,也知道怎么写简单的装饰器。但大多数人对装饰器的进阶用法,比如带参数的装饰器、...

Python 类型提示实战指南:让代码更健壮

类型提示并不是强制执行的类型系统,而是一种可选的代码文档化工具。它通过注解函数参数和返回值的类型,帮助开发者更清晰地表达意图,同时让 IDE 和类型检查器(如 mypy)能够提前发现潜在的类型错误。...

发表评论

访客

看不清,换一张

◎欢迎参与讨论,请在这里发表您的看法和观点。