Python装饰器用法详解

1. 引言

装饰器是Python中一项强大而优雅的特性,它允许在不修改原函数代码的情况下,为函数添加额外的功能。装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外行为。这种设计模式在Python中被广泛应用,从日志记录、性能测试到事务处理、权限验证等场景。本文将深入探讨Python装饰器的各种用法,从基础概念到高级技巧,通过详尽的代码示例和解释,帮助读者全面掌握这一重要特性。

2. 函数基础复习

在深入装饰器之前,我们需要回顾一些Python函数的基础知识,因为装饰器的工作原理与函数的这些特性密切相关。

2.1. 函数是对象

在Python中,函数是一等公民,这意味着函数可以像其他对象一样被传递和操作:

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

# 函数可以赋值给变量
say_hello = greet

# 通过新变量调用函数
print(say_hello("Alice"))  # 输出: Hello, Alice!

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

print(call_func(greet, "Bob"))  # 输出: Hello, Bob!

2.2. 函数嵌套与闭包

Python允许在函数内部定义其他函数,这种嵌套函数可以访问外部函数的变量:

def outer_function(msg):
    message = msg

    def inner_function():
        print(message)

    return inner_function

# 调用outer_function会返回inner_function
my_func = outer_function("Hi, there!")
my_func()  # 输出: Hi, there!

这种内部函数可以访问外部函数变量的特性称为闭包。闭包使得内部函数可以记住它被创建时的环境。

3. 装饰器的基本概念

装饰器本质上是一个接受函数作为参数并返回一个新函数的函数。它的核心思想是在不改变原函数代码的情况下,为函数添加额外的功能。

3.1. 简单装饰器示例

下面是一个最简单的装饰器,它在函数执行前后打印一些信息:

def simple_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

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

# 使用装饰器增强say_hello函数
decorated_hello = simple_decorator(say_hello)
decorated_hello()

输出:

Something is happening before the function is called.
Hello!
Something is happening after the function is called.

3.2. 使用@语法糖

Python提供了更简洁的装饰器语法——@符号。上面的例子可以重写为:

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

say_hello()

@语法糖等同于:say_hello = simple_decorator(say_hello)

4. 处理带参数的函数

上面的装饰器只能处理不带参数的函数。为了使装饰器能处理任意参数的函数,我们需要使用*argskwargs

def decorator_with_arguments(func):
    def wrapper(*args, **kwargs):
        print("Function arguments:")
        print(f"Positional: {args}")
        print(f"Keyword: {kwargs}")
        return func(*args, **kwargs)
    return wrapper

@decorator_with_arguments
def greet(name, greeting="Hello"):
    print(f"{greeting}, {name}!")

greet("Alice")
greet("Bob", greeting="Hi")

输出:

Function arguments:
Positional: ('Alice',)
Keyword: {}
Hello, Alice!
Function arguments:
Positional: ('Bob',)
Keyword: {'greeting': 'Hi'}
Hi, Bob!

5. 带参数的装饰器

有时我们需要创建可以接受参数的装饰器。这需要再增加一层嵌套:

def repeat(num_times):
    def decorator_repeat(func):
        def wrapper(*args, **kwargs):
            for _ in range(num_times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator_repeat

@repeat(num_times=3)
def greet(name):
    print(f"Hello, {name}!")

greet("World")

输出:

Hello, World!
Hello, World!
Hello, World!

这里,repeat是一个接受参数的装饰器工厂,它返回一个真正的装饰器。

6. 保留原函数的元信息

使用装饰器后,原函数的元信息(如函数名、文档字符串等)会被装饰器函数覆盖。我们可以使用functools.wraps来保留这些信息:

from functools import wraps

def decorator_with_metadata(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        """Wrapper function's docstring"""
        print(f"Calling function: {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

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

print(greet.__name__)  # 输出: greet (而不是 wrapper)
print(greet.__doc__)   # 输出: Greet someone by name (而不是 Wrapper function's docstring)

7. 装饰器类

虽然装饰器通常使用函数实现,但也可以使用类实现。类装饰器需要实现__call__方法:

class CountCalls:
    def __init__(self, func):
        self.func = func
        self.num_calls = 0

    def __call__(self, *args, **kwargs):
        self.num_calls += 1
        print(f"Call {self.num_calls} of {self.func.__name__}")
        return self.func(*args, **kwargs)

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

say_hello()
say_hello()
say_hello()

输出:

Call 1 of say_hello
Hello!
Call 2 of say_hello
Hello!
Call 3 of say_hello
Hello!

8. 多个装饰器

可以同时使用多个装饰器,它们的执行顺序是从下到上:

def decorator1(func):
    def wrapper(*args, **kwargs):
        print("Decorator 1 before")
        result = func(*args, **kwargs)
        print("Decorator 1 after")
        return result
    return wrapper

def decorator2(func):
    def wrapper(*args, **kwargs):
        print("Decorator 2 before")
        result = func(*args, **kwargs)
        print("Decorator 2 after")
        return result
    return wrapper

@decorator1
@decorator2
def test():
    print("Executing test function")

test()

输出:

Decorator 1 before
Decorator 2 before
Executing test function
Decorator 2 after
Decorator 1 after

这等价于:test = decorator1(decorator2(test))

9. 内置装饰器

Python提供了一些内置装饰器,其中最常用的是@property@classmethod@staticmethod

9.1. @property装饰器

@property装饰器将方法转换为属性,使得可以像访问属性一样调用方法:

class Circle:
    def __init__(self, radius):
        self.radius = radius

    @property
    def diameter(self):
        return 2 * self.radius

    @property
    def area(self):
        return 3.14159 * self.radius ** 2

circle = Circle(5)
print(circle.diameter)  # 输出: 10
print(circle.area)      # 输出: 78.53975

9.2. @classmethod和@staticmethod

@classmethod装饰器将方法转换为类方法,第一个参数是类本身(cls)。@staticmethod装饰器将方法转换为静态方法,不需要特殊参数:

class MyClass:
    @classmethod
    def class_method(cls):
        print(f"Called class method of {cls.__name__}")

    @staticmethod
    def static_method():
        print("Called static method")

MyClass.class_method()    # 输出: Called class method of MyClass
MyClass.static_method()   # 输出: Called static method

10. 装饰器的应用场景

装饰器在实际开发中有广泛的应用,以下是一些常见场景:

10.1. 日志记录

def log_function_calls(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__} with args: {args}, kwargs: {kwargs}")
        result = func(*args, **kwargs)
        print(f"{func.__name__} returned: {result}")
        return result
    return wrapper

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

add(3, 5)

输出:

Calling add with args: (3, 5), kwargs: {}
add returned: 8

10.2. 性能测试

import time
from functools import wraps

def measure_time(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"{func.__name__} executed in {end_time - start_time:.6f} seconds")
        return result
    return wrapper

@measure_time
def calculate_sum(n):
    return sum(range(n))

calculate_sum(1000000)

输出:

calculate_sum executed in 0.037102 seconds

10.3. 权限验证

def requires_permission(permission):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            current_user = get_current_user()  # 假设这个函数获取当前用户
            if current_user.has_permission(permission):
                return func(*args, **kwargs)
            else:
                raise PermissionError(f"User lacks {permission} permission")
        return wrapper
    return decorator

@requires_permission("admin")
def delete_user(user_id):
    # 删除用户逻辑
    pass

10.4. 缓存结果

from functools import wraps

def cache_results(func):
    cache = {}

    @wraps(func)
    def wrapper(*args):
        if args in cache:
            print("Returning cached result")
            return cache[args]
        result = func(*args)
        cache[args] = result
        return result
    return wrapper

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

print(fibonacci(10))
print(fibonacci(10))  # 第二次调用会使用缓存

11. 高级装饰器技巧

11.1. 有状态的装饰器

装饰器可以携带状态,记住函数调用之间的信息:

def counter(func):
    func.calls = 0

    @wraps(func)
    def wrapper(*args, **kwargs):
        func.calls += 1
        print(f"{func.__name__} has been called {func.calls} times")
        return func(*args, **kwargs)
    return wrapper

@counter
def greet():
    print("Hello!")

greet()
greet()
greet()

输出:

greet has been called 1 times
Hello!
greet has been called 2 times
Hello!
greet has been called 3 times
Hello!

11.2. 装饰器类与函数混合

可以创建既可以作为装饰器使用,又可以作为普通调用的装饰器:

class optional_debug:
    def __init__(self, func=None, *, debug=False):
        self.func = func
        self.debug = debug

    def __call__(self, *args, **kwargs):
        if self.debug:
            print(f"Calling {self.func.__name__}")
        return self.func(*args, **kwargs)

    def __call__(self, func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            if self.debug:
                print(f"Calling {func.__name__}")
            return func(*args, **kwargs)
        return wrapper

# 使用方式1:作为普通装饰器
@optional_debug
def add(a, b):
    return a + b

# 使用方式2:带参数的装饰器
@optional_debug(debug=True)
def multiply(a, b):
    return a * b

add(2, 3)
multiply(4, 5)

11.3. 装饰器与类

装饰器不仅可以用于函数,也可以用于类:

def add_methods(cls):
    def new_method(self):
        print("New method added by decorator")

    cls.new_method = new_method
    return cls

@add_methods
class MyClass:
    pass

obj = MyClass()
obj.new_method()  # 输出: New method added by decorator

11.4. 使用functools.update_wrapper

虽然@wraps是更常用的方式,但也可以使用functools.update_wrapper手动复制元信息:

from functools import update_wrapper

def simple_decorator(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    update_wrapper(wrapper, func)
    return wrapper

@simple_decorator
def example():
    """Example function docstring"""
    pass

print(example.__name__)  # 输出: example
print(example.__doc__)   # 输出: Example function docstring

12. 总结

Python装饰器是一种强大而灵活的工具,它允许我们在不修改原函数代码的情况下扩展其功能。通过本文的学习,我们了解到:

装饰器本质上是接受函数作为参数并返回新函数的函数

使用@语法糖可以简洁地应用装饰器

装饰器可以处理带参数的函数,通过*argskwargs捕获所有参数

带参数的装饰器需要三层嵌套结构

使用functools.wraps可以保留原函数的元信息

装饰器可以使用类实现,通过__call__方法使类可调用

多个装饰器可以组合使用,执行顺序是从下到上

Python内置了@property@classmethod@staticmethod等常用装饰器

装饰器在实际开发中有广泛应用,如日志记录、性能测试、权限验证和结果缓存等

高级装饰器技巧包括有状态装饰器、多功能装饰器、类装饰器和手动更新元信息等

掌握装饰器不仅能够提高代码的可读性和可维护性,还能帮助我们以更优雅的方式实现横切关注点(如日志、缓存、权限等),是Python开发者必备的高级技能之一。通过不断实践和探索,您将能够充分利用装饰器的强大功能,写出更加简洁、高效和Pythonic的代码。

发表回复

后才能评论