Python装饰器(decorator)

Python装饰器(decorator)是一种高阶函数,用于在不修改原函数代码的情况下,动态地为函数添加额外的功能。它本质上是一个接受函数作为输入并返回新函数的函数,常用于日志记录、性能测试、权限验证等场景。以下是关于Python装饰器的详细讲解:


1. 基本概念

装饰器是一个函数,它接受一个函数作为参数,并返回一个新的函数。新函数通常会在调用原函数前后执行一些额外的逻辑。装饰器的语法糖是 @decorator_name,放在函数定义的上方。

简单例子
def my_decorator(func):
    def wrapper():
        print("Something is happening before the function is called.")
        func()  # 调用原函数
        print("Something is happening after the function is called.")
    return wrapper

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

say_hello()

输出:

Something is happening before the function is called.
Hello!
Something is happening after the function is called.
  • my_decorator 是一个装饰器,接受函数 func 作为参数。
  • wrapper 是内部定义的包装函数,扩展了 func 的行为。
  • @my_decorator 等价于 say_hello = my_decorator(say_hello)

2. 带参数的函数装饰器

如果被装饰的函数有参数,装饰器需要处理这些参数。可以使用 *args**kwargs 来支持任意参数。

例子
def my_decorator(func):
    def wrapper(*args, **kwargs):
        print("Before the function call.")
        result = func(*args, **kwargs)  # 调用原函数并保存返回值
        print("After the function call.")
        return result  # 返回原函数的结果
    return wrapper

@my_decorator
def greet(name):
    return f"Hello, {name}!"

print(greet("Alice"))

输出:

Before the function call.
Hello, Alice!
After the function call.
Hello, Alice!

3. 带参数的装饰器

有时装饰器本身需要接受参数,这需要再加一层函数来处理装饰器的参数。

例子:带参数的装饰器
def repeat(n):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for _ in range(n):
                func(*args, **kwargs)
        return wrapper
    return decorator

@repeat(3)  # 重复执行3次
def say_hello():
    print("Hello!")

say_hello()

输出:

Hello!
Hello!
Hello!
  • repeat(n) 是装饰器工厂,返回一个装饰器。
  • @repeat(3) 表示函数 say_hello 将被重复调用3次。

4. 保留函数元信息

默认情况下,装饰器会覆盖原函数的元信息(如 __name____doc__)。为了保留这些信息,可以使用 functools.wraps

例子
from functools import wraps

def my_decorator(func):
    @wraps(func)  # 保留原函数的元信息
    def wrapper(*args, **kwargs):
        print("Before the function call.")
        result = func(*args, **kwargs)
        print("After the function call.")
        return result
    return wrapper

@my_decorator
def greet(name):
    """Greet someone."""
    return f"Hello, {name}!"

print(greet.__name__)  # 输出:greet
print(greet.__doc__)   # 输出:Greet someone.
  • @wraps(func) 确保 wrapper 的元信息与原函数一致。

5. 常见使用场景

装饰器在实际开发中有许多用途,例如:

  1. 日志记录
def log(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__} with {args}, {kwargs}")
        result = func(*args, **kwargs)
        print(f"{func.__name__} returned {result}")
        return result
    return wrapper

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

add(2, 3)

输出:

Calling add with (2, 3), {}
add returned 5
  1. 计时器
import time
from functools import wraps

def timer(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"{func.__name__} took {end - start:.4f} seconds")
        return result
    return wrapper

@timer
def slow_function():
    time.sleep(1)

slow_function()

输出:

slow_function took 1.0023 seconds
  1. 权限验证
def require_permission(permission):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            if permission in user_permissions:  # 假设 user_permissions 是全局变量
                return func(*args, **kwargs)
            else:
                raise PermissionError("Permission denied")
        return wrapper
    return decorator

user_permissions = ["admin"]

@require_permission("admin")
def delete_user(user_id):
    print(f"User {user_id} deleted")

delete_user(123)

输出:

User 123 deleted

6. 类装饰器

除了函数装饰器,类也可以用作装饰器。类装饰器通常通过实现 __call__ 方法来包装函数。

例子
class MyDecorator:
    def __init__(self, func):
        self.func = func
    
    def __call__(self, *args, **kwargs):
        print("Before the function call.")
        result = self.func(*args, **kwargs)
        print("After the function call.")
        return result

@MyDecorator
def greet(name):
    return f"Hello, {name}!"

print(greet("Alice"))

输出:

Before the function call.
Hello, Alice!
After the function call.
Hello, Alice!

7. 多个装饰器

一个函数可以应用多个装饰器,执行顺序是从内到外(从靠近函数的装饰器开始)。

例子
def deco1(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print("Decorator 1")
        return func(*args, **kwargs)
    return wrapper

def deco2(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print("Decorator 2")
        return func(*args, **kwargs)
    return wrapper

@deco1
@deco2
def say_hello():
    print("Hello!")

say_hello()

输出:

Decorator 1
Decorator 2
Hello!
  • deco2 先执行(离函数最近),然后是 deco1

8. 内置装饰器

Python 提供了一些内置装饰器,常用的有:

  • @staticmethod:定义静态方法,不需要实例化即可调用。
  • @classmethod:定义类方法,第一个参数是类本身(通常命名为 cls)。
  • @property:将方法转换为属性,方便访问和设置。
例子:@property
class Person:
    def __init__(self, name):
        self._name = name
    
    @property
    def name(self):
        return self._name
    
    @name.setter
    def name(self, value):
        self._name = value

p = Person("Alice")
print(p.name)  # 输出:Alice
p.name = "Bob"
print(p.name)  # 输出:Bob

9. 注意事项

  1. 性能开销:装饰器会增加函数调用开销,尤其是在嵌套多个装饰器时。
  2. 调试难度:装饰器可能使调用栈复杂化,使用 functools.wraps 可以缓解元信息丢失问题。
  3. 副作用:确保装饰器逻辑不会意外修改函数的行为或返回值。

总结

Python装饰器是函数式编程的强大工具,能够优雅地扩展函数功能。掌握装饰器的核心在于理解闭包和高阶函数。通过合理使用装饰器,可以写出更简洁、可维护的代码,尤其在需要复用逻辑时(如日志、计时、权限控制等)。

你可能感兴趣的:(Python装饰器(decorator))