Python中的装饰器:函数的化妆师详解

文章目录

  • Python中的装饰器:函数的"化妆师"详解
    • 一、装饰器基础概念
      • 什么是装饰器?
      • 装饰器的基本语法
    • 二、装饰器的工作原理
      • 1. 函数作为一等公民
      • 2. 高阶函数
      • 3. 闭包
    • 三、编写自定义装饰器
      • 1. 基本装饰器示例
      • 2. 带参数的函数装饰
      • 3. 带参数的装饰器
    • 四、内置装饰器
      • 1. `@property`
      • 2. `@classmethod` 和 `@staticmethod`
      • 3. `@functools.wraps`
    • 五、装饰器的实际应用
      • 1. 计时装饰器
      • 2. 缓存装饰器
      • 3. 权限验证装饰器
      • 4. 日志记录装饰器
    • 六、装饰器的堆叠
    • 七、类装饰器
    • 八、装饰器的注意事项
    • 九、总结

Python中的装饰器:函数的"化妆师"详解

装饰器(Decorator)是Python中一种强大的语法特性,它允许你在不修改原函数代码的情况下,为函数添加额外的功能。就像化妆师可以为演员添加不同的妆容而不改变演员本身一样,装饰器可以"装饰"函数,为其添加各种"修饰"功能。

一、装饰器基础概念

什么是装饰器?

装饰器本质上是一个Python函数(或其他可调用对象),它接受一个函数作为输入并返回一个新的函数。通过装饰器,我们可以在不修改原函数代码的情况下,为函数添加日志记录、性能测试、权限校验、缓存等额外功能。

装饰器的基本语法

@decorator
def function():
    pass

这等同于:

def function():
    pass
function = decorator(function)

二、装饰器的工作原理

1. 函数作为一等公民

在Python中,函数是"一等公民",这意味着:

  • 函数可以被赋值给变量
  • 函数可以作为参数传递给其他函数
  • 函数可以作为其他函数的返回值
  • 函数可以在运行时动态创建
def greet(name):
    return f"Hello, {name}!"

# 函数赋值给变量
say_hello = greet
print(say_hello("Alice"))  # 输出: Hello, Alice!

2. 高阶函数

高阶函数是指能够接受函数作为参数,或者返回函数作为结果的函数。装饰器就是利用了高阶函数的特性。

def make_adder(n):
    def adder(x):
        return x + n
    return adder

add5 = make_adder(5)
print(add5(3))  # 输出: 8

3. 闭包

闭包是指内部函数可以访问外部函数作用域中的变量,即使外部函数已经执行完毕。装饰器通常使用闭包来保存状态。

def outer():
    message = "Hello"
    
    def inner():
        print(message)
    
    return inner

my_func = outer()
my_func()  # 输出: Hello

三、编写自定义装饰器

1. 基本装饰器示例

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.
"""

2. 带参数的函数装饰

def decorator_with_args(func):
    def wrapper(*args, **kwargs):
        print("Decorator is working")
        return func(*args, **kwargs)
    return wrapper

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

print(greet("Alice"))  
"""
输出:
Decorator is working
Hello, Alice
"""

3. 带参数的装饰器

有时我们需要装饰器本身也能接受参数:

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
"""

四、内置装饰器

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

1. @property

将方法转换为属性:

class Circle:
    def __init__(self, radius):
        self._radius = radius
    
    @property
    def radius(self):
        return self._radius
    
    @radius.setter
    def radius(self, value):
        if value >= 0:
            self._radius = value
        else:
            raise ValueError("Radius must be positive")

circle = Circle(5)
print(circle.radius)  # 输出: 5
circle.radius = 10
print(circle.radius)  # 输出: 10

2. @classmethod@staticmethod

class MyClass:
    @classmethod
    def class_method(cls):
        print(f"Called class_method of {cls}")
    
    @staticmethod
    def static_method():
        print("Called static_method")

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

3. @functools.wraps

当使用装饰器时,原函数的元信息(如__name____doc__)会被替换为装饰器内部函数的元信息。functools.wraps可以保留这些元信息:

from functools import wraps

def my_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        """Wrapper docstring"""
        print("Something is happening before the function is called.")
        return func(*args, **kwargs)
    return wrapper

@my_decorator
def say_hello():
    """Say hello docstring"""
    print("Hello!")

print(say_hello.__name__)  # 输出: say_hello
print(say_hello.__doc__)   # 输出: Say hello docstring

五、装饰器的实际应用

1. 计时装饰器

import time

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

@timer
def waste_time(n):
    for _ in range(n):
        sum([i**2 for i in range(1000)])

waste_time(1000)

2. 缓存装饰器

from functools import lru_cache

@lru_cache(maxsize=None)
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

print(fibonacci(30))  # 计算结果会被缓存

3. 权限验证装饰器

def requires_admin(func):
    @wraps(func)
    def wrapper(user, *args, **kwargs):
        if user.get('is_admin'):
            return func(user, *args, **kwargs)
        else:
            raise PermissionError("You must be an admin to perform this action")
    return wrapper

@requires_admin
def delete_database(user):
    print("Database deleted!")

admin_user = {'username': 'admin', 'is_admin': True}
regular_user = {'username': 'user', 'is_admin': False}

delete_database(admin_user)  # 正常执行
delete_database(regular_user)  # 抛出PermissionError

4. 日志记录装饰器

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

@log_activity
def add_numbers(a, b):
    return a + b

add_numbers(3, 5)
"""
输出:
Calling function add_numbers with args: (3, 5), kwargs: {}
Function add_numbers returned: 8
"""

六、装饰器的堆叠

可以在一个函数上应用多个装饰器,它们会从下往上依次执行:

def decorator1(func):
    def wrapper():
        print("Decorator 1 before")
        func()
        print("Decorator 1 after")
    return wrapper

def decorator2(func):
    def wrapper():
        print("Decorator 2 before")
        func()
        print("Decorator 2 after")
    return wrapper

@decorator1
@decorator2
def say_hello():
    print("Hello!")

say_hello()
"""
输出:
Decorator 1 before
Decorator 2 before
Hello!
Decorator 2 after
Decorator 1 after
"""

七、类装饰器

除了函数装饰器,Python还支持类装饰器。类装饰器是一个接受类作为参数并返回一个新类的可调用对象:

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("Initializing database")

db1 = Database()
db2 = Database()
print(db1 is db2)  # 输出: True

八、装饰器的注意事项

  1. 装饰器会改变函数的元信息:使用@functools.wraps来保留原函数的元信息
  2. 装饰器在导入时执行:装饰器代码在模块导入时就会执行,而不是在函数调用时
  3. 调试装饰的函数可能更困难:因为调用栈会多一层
  4. 过度使用装饰器会使代码难以理解:适度使用装饰器可以提高代码可读性,但过度使用会适得其反

九、总结

装饰器是Python中非常强大的特性,它允许我们:

  • 在不修改原函数代码的情况下扩展函数功能
  • 实现代码复用和关注点分离
  • 保持代码整洁和可读性
  • 实现AOP(面向切面编程)风格的编程

通过合理使用装饰器,我们可以写出更加优雅、可维护的Python代码。从简单的日志记录到复杂的权限系统,装饰器都能大显身手。掌握装饰器是成为Python高级程序员的重要一步。

你可能感兴趣的:(python,开发语言,有问必答)