Python高级技术之:`Python`的`decorator`:从简单装饰器到带参数、带状态装饰器的设计模式。

嘿,各位编程界的弄潮儿,今天咱们聊点有意思的——Python 装饰器!别听到“装饰器”三个字就觉得高深莫测,它其实就像给你的函数穿件“定制款外套”,让它在不改变自身结构的情况下,拥有更多技能。

开场白:函数,你的衣服呢?

想象一下,你是一个函数,每天兢兢业业地完成任务。突然有一天,老板跟你说:“小伙子,你很棒,但是现在我们需要你每次工作前先打个卡,工作后写个报告。” 你的第一反应肯定是:“啥?还要改我的代码?这么麻烦!”

这时候,装饰器就闪亮登场了。它就像一个裁缝,不用动你的“函数本体”,就能给你缝制一件“外套”,让你自动完成打卡和写报告的任务。

第一部分:最简单的装饰器:给函数加个“小尾巴”

咱们先从最简单的装饰器开始,看看它是怎么工作的。

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

say_hello() # 输出: Hello!

# 定义一个简单的装饰器
def my_decorator(func):
  def wrapper():
    print("Before calling the function.")
    func()
    print("After calling the function.")
  return wrapper

# 使用装饰器
@my_decorator
def say_hello():
  print("Hello!")

say_hello() # 输出:
# Before calling the function.
# Hello!
# After calling the function.

这段代码里,my_decorator 就是一个装饰器。它接收一个函数 func 作为参数,返回一个新的函数 wrapperwrapper 函数在执行 func 之前和之后,都加上了一些额外的操作(打印信息)。

@my_decorator 这一行代码,就是“语法糖”,它等价于:

say_hello = my_decorator(say_hello)

也就是说,say_hello 现在指向了 wrapper 函数。 当你调用 say_hello() 时,实际上是在执行 wrapper() 函数。

总结一下:

  • 装饰器本质上是一个函数,它接收一个函数作为参数,并返回一个新的函数。
  • @decorator_name 语法糖,简化了装饰器的使用。

第二部分:让装饰器“聪明”一点:带参数的函数怎么办?

如果被装饰的函数需要接收参数,那我们的装饰器也需要“聪明”一点,能够处理这些参数。

def my_decorator(func):
  def wrapper(*args, **kwargs):
    print("Before calling the function.")
    result = func(*args, **kwargs)
    print("After calling the function.")
    return result
  return wrapper

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

greeting = greet("Alice") # 输出:
# Before calling the function.
# Hello, Alice!
# After calling the function.

print(greeting) # 输出: Greeting: Hello, Alice!

这里,wrapper 函数使用了 *args**kwargs 来接收任意数量的位置参数和关键字参数,并将它们传递给被装饰的函数 func。这样,无论 greet 函数接收什么参数,my_decorator 都能正确地处理。

重点:

  • *args 用于接收任意数量的位置参数。
  • **kwargs 用于接收任意数量的关键字参数。
  • 务必将 func 的返回值返回,否则被装饰的函数可能无法正常工作。

第三部分:给装饰器加点“个性”:带参数的装饰器

有时候,我们希望装饰器的行为能够根据一些参数进行调整。例如,我们可能希望控制在函数执行前后打印信息的格式。这时候,我们就需要带参数的装饰器。

def log_decorator(log_message):
  def decorator(func):
    def wrapper(*args, **kwargs):
      print(f"Log: {log_message} - Before calling {func.__name__}")
      result = func(*args, **kwargs)
      print(f"Log: {log_message} - After calling {func.__name__}")
      return result
    return wrapper
  return decorator

@log_decorator("Important")
def calculate(x, y):
  print(f"Calculating {x} + {y}")
  return x + y

result = calculate(5, 3) # 输出:
# Log: Important - Before calling calculate
# Calculating 5 + 3
# Log: Important - After calling calculate

print(result) # 输出: 8

这个例子中,log_decorator 接收一个参数 log_message,然后返回一个真正的装饰器 decoratordecorator 接收被装饰的函数 func,并返回 wrapper 函数。

分析:

  1. @log_decorator("Important") 实际上是先调用 log_decorator("Important"),返回 decorator 函数。
  2. 然后,@decorator 作用于 calculate 函数。

精髓: 带参数的装饰器实际上是两层函数嵌套。外层函数接收装饰器的参数,内层函数才是真正的装饰器。

第四部分:让装饰器“记住”点什么:带状态的装饰器

有时候,我们希望装饰器能够“记住”一些信息,例如函数被调用的次数。 这时候,我们就需要带状态的装饰器。

def count_calls(func):
  def wrapper(*args, **kwargs):
    wrapper.call_count += 1
    print(f"Function {func.__name__} called {wrapper.call_count} times")
    return func(*args, **kwargs)
  wrapper.call_count = 0  # 初始化调用次数
  return wrapper

@count_calls
def my_function():
  print("Executing my_function")

my_function() # 输出:
# Function my_function called 1 times
# Executing my_function

my_function() # 输出:
# Function my_function called 2 times
# Executing my_function

这个例子中,我们在 wrapper 函数中添加了一个 call_count 属性,用于记录函数被调用的次数。每次调用 wrapper 函数时,call_count 都会加 1。

关键:

  • wrapper 函数外部初始化 call_count 属性。
  • 使用 wrapper.call_count 来访问和修改这个属性。

第五部分:更优雅的带状态装饰器:使用类

使用类来实现带状态的装饰器,代码会更清晰、更易于维护。

class CallCounter:
  def __init__(self, func):
    self.func = func
    self.call_count = 0

  def __call__(self, *args, **kwargs):
    self.call_count += 1
    print(f"Function {self.func.__name__} called {self.call_count} times")
    return self.func(*args, **kwargs)

@CallCounter
def my_function():
  print("Executing my_function")

my_function() # 输出:
# Function my_function called 1 times
# Executing my_function

my_function() # 输出:
# Function my_function called 2 times
# Executing my_function

这个例子中,CallCounter 类实现了 __init____call__ 方法。

  • __init__ 方法接收被装饰的函数 func,并初始化 call_count 属性。
  • __call__ 方法让类的实例可以像函数一样被调用。 每次调用实例时,call_count 都会加 1。

优势:

  • 代码结构更清晰,更容易理解和维护。
  • 可以使用类的属性和方法来管理装饰器的状态。

第六部分:装饰器的应用场景

装饰器在实际开发中有很多应用场景,例如:

  • 日志记录: 记录函数的调用信息,方便调试和问题排查。
  • 性能测试: 测量函数的执行时间,优化代码性能。
  • 缓存: 缓存函数的计算结果,避免重复计算。
  • 权限验证: 验证用户的权限,控制对函数的访问。
  • 重试机制: 在函数执行失败时自动重试。
  • 类型检查: 检查函数的参数类型,提高代码的健壮性。

举例:权限验证

def requires_permission(permission):
  def decorator(func):
    def wrapper(*args, **kwargs):
      user = kwargs.get('user')  # 假设函数接收一个 user 参数
      if user and user.has_permission(permission):
        return func(*args, **kwargs)
      else:
        print(f"User does not have permission: {permission}")
        return None  # 或者抛出异常
    return wrapper
  return decorator

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

  def has_permission(self, permission):
    return permission in self.permissions

@requires_permission("edit")
def edit_document(document_id, user):
  print(f"User {user.name} is editing document {document_id}")
  return f"Document {document_id} edited by {user.name}"

user1 = User("Alice", ["view", "edit"])
user2 = User("Bob", ["view"])

edit_document(123, user=user1) # 输出: User Alice is editing document 123
edit_document(456, user=user2) # 输出: User does not have permission: edit

第七部分:装饰器的注意事项

  • 避免循环引用: 装饰器可能会导致循环引用,从而引发内存泄漏。
  • 装饰器的顺序: 多个装饰器的顺序很重要,不同的顺序可能会导致不同的结果。
  • 保留函数元信息: 装饰器会修改函数的元信息 (例如 __name__, __doc__),可以使用 functools.wraps 来保留原始函数的元信息。
import functools

def my_decorator(func):
  @functools.wraps(func) # 保留func的元信息
  def wrapper(*args, **kwargs):
    """Wrapper function docstring"""
    print("Before calling the function.")
    result = func(*args, **kwargs)
    print("After calling the function.")
    return result
  return wrapper

@my_decorator
def say_hello():
  """Original function docstring"""
  print("Hello!")

print(say_hello.__name__) # 输出: say_hello
print(say_hello.__doc__)  # 输出: Original function docstring

第八部分:装饰器设计模式总结

特性 简单装饰器 带参数的装饰器 带状态的装饰器 基于类的装饰器
结构 单层函数嵌套 双层函数嵌套 单层或多层函数嵌套,依赖于外部变量 定义一个类,实现 __init____call__ 方法
功能 在函数执行前后添加额外的操作 根据参数调整装饰器的行为 记录和维护装饰器的状态,例如函数调用次数 代码结构更清晰,更容易管理状态
应用场景 日志记录、性能测试 (最简单的场景) 灵活配置日志格式、权限验证 缓存、重试机制 复杂的权限管理、自定义行为控制
优点 简单易懂 灵活性高 可以实现更复杂的功能 面向对象的设计,代码可读性高
缺点 功能有限 理解起来稍微复杂 需要注意状态的维护 稍微复杂,需要理解类的概念
关键点 理解函数作为一等公民的概念 理解闭包的概念 wrapper 函数外部初始化状态变量 实现 __call__ 方法,让类的实例可以像函数一样被调用

结束语:装饰器,你的编程瑞士军刀

装饰器是 Python 中一个非常强大的特性,它可以让你在不修改原有代码的情况下,扩展函数的功能。 掌握装饰器,你就能写出更简洁、更优雅、更易于维护的代码。所以,赶紧拿起你的键盘,开始尝试使用装饰器吧!相信我,你会爱上它的! 记住,编程就像生活,需要不断地装饰,才能变得更加精彩!

下次见,各位!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注