开发者

Python装饰器decorator实际应用与高级使用详解

目录
  • 1. 什么是装饰器?(核心思想)
  • 2. 为什么需要装饰器?(动机)
  • 3. python基础:理解装饰器的前提
  • 4. 一步步构建你的第一个装饰器
    • 最简单的装饰器(手动方式)
    • 使用@语法糖
  • 5. 处理带参数的函数
    • 6.functools.wraps的重要性
      • 7. 带参数的装饰器(进阶)
        • 8. 类装饰器(进阶)
          • 9. 装饰器的实际应用场景(重点)
            • 日志记录 (Logging)
            • 性能计时 (Timing)
            • 用户认证 (Authentication)
            • 缓存 (Caching/Memoization)
            • 输入验证 (Validation)
          • 10. 装饰器栈(多个装饰器)
            • 11. 总结

              在这篇文章中,我们来详细、系统地讲解一下Python的装饰器(Decorator)。我会从基本概念讲起,循序渐进,直到实际应用和高级用法。

              1. 什么是装饰器?(核心思想)

              装饰器本质上是一个Python函数,它可以让其他函数在不改变其源代码的情况下增加额外的功能。

              核心思想包装 (Wrapping)

              你可以把装饰器想象成一个包装礼物的过程:

              • 你的函数 = 礼物本身
              • 装饰器 = 包装纸、彩带和蝴蝶结

              礼物(函数)的核心功能没有变,但经过包装(装饰器)后,它看起来更漂亮、更完整了(增加了新功能)。

              在代码层面,装饰器是一个接收函数作为参数,并返回一个新函数的函数。

              2. 为什么需要装饰器?(动机)

              假设我们有很多函数,现在想给每个函数都加上一个“计算运行时间”的功能。

              不好的方法:修改每个函数

              import time
              
              def func_A():
                  start_time = time.time()
                  print("函数A开始执行...")
                  time.sleep(1)
                  print("函数A执行完毕。")
                  end_time = time.time()
                  print(f"函数A耗时: {end_time - start_编程time:.2f}秒")
              
              def func_B():
                  start_time = time.time()
                  print("函数B开始执行...")
                  time.sleep(2)
                  print("函数B执行完毕。")
                  end_time = time.time()
                  print(f"函数B耗时: {end_time - start_time:.2f}秒")
              
              # ... 还有 func_C, func_D ...
              

              问题:

              • 代码冗余:计时逻辑在每个函数里都重复了一遍。
              • 违反“开放封闭原则&rdwww.devze.comquo;:每次新增需求(比如再加个日志功能),都要去修改已有函数的代码。

              装饰器就是为了解决这类问题而生的,它能将这些通用的、与核心业务无关的功能(如计时、日志、认证)抽离出来。

              3. Python基础:理解装饰器的前提

              要理解装饰器,必须先理解Python中 “函数是一等公民” (Functions are First-Class Objects) 的概念。这意味着函数可以:

              被赋值给一个变量

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

              作为参数传递给另一个函数

              def do_something(func):
                  print("准备做某事...")
                  func()  # 调用传入的函数
                  print("做完了。")
              
              def work():
                  print("正在努力工作...")
              
              do_something(work)
              # 输出:
              # 准备做某事...
              # 正在努力工作...
              # 做完了。
              

              在函数内部定义,并作为返回值返回

              def get_greeter():
                  def greet():
                      print("你好,世界!")
                  return greet  # 返回内部定义的greet函数
              
              greeter_func = get_greeter()
              greeter_func()  # 输出: 你好,世界!
              

              装饰器正是利用了这几点特性,尤其是第2点和第3点。

              4. 一步步构建你的第一个装饰器

              最简单的装饰器(手动方式)

              我们来写一个简单的装饰器,它在函数执行前后打印信息。

              # 1. 定义一个装饰器函数
              def my_decorator(func):
                  # 2. 定义一个内部包装函数,它将“包装”原始函数
                  def wrapper():
                      print("在被装饰的函数执行之前...")
                      func()  # 3. 调用原始函数
                      print("在被装饰的函数执行之后...")
                  
                  # 4. 返回这个包装好的函数
                  return wrapper
              
              # 定义一个需要被装饰的函数
              def say_whee():
                  print("Whee!")
              
              # 手动使用装饰器
              say_whee = my_decorator(say_whejavascripte)
              
              # 现在调用say_whee,实际上是在调用wrapper函数
              say_whee()
              

              输出:

              在被装饰的函数执行之前...
              Whee!
              在被装饰的函数执行之后...
              

              say_whee = my_decorator(say_whee) 这行代码是理解装饰器的关键。

              它用 my_decorator 返回的 wrapper 函数覆盖了原来的 say_whee 函数。

              使用@语法糖

              Python提供了一个更优雅、更Pythonic的方式来使用装饰器,那就是 @ 语法糖。

              def my_decorator(func):
                  def wrapper():
                      print("在被装饰的函数执行之前...")
                      func()
                      print("在被装饰的函数执行之后...")
                  return wrapper
              
              @my_decorator  # 这行等价于 say_whee = my_decorator(say_whee)
              def say_whee():
                  print("Whee!")
              
              say_whee()
              

              输出和上面完全一样,但代码是不是简洁多了?

              5. 处理带参数的函数

              如果我们的原始函数带参数怎么办?比如 greet(name)。上面的 wrapper 函数不接受任何参数,会报错。

              我们需要让 wrapper 函数能接受任意参数,并把它们传递给原始函数。这里就要用到 *args**kwargs

              def decorator_with_args(func):
                  # wrapper现在可以接受任意数量的位置参数和关键字参数
                  def wrapper(*args, **kwargs):
                      print("装饰器:函数开始执行")
                      result = func(*args, **kwargs)  # 将参数传递给原始函数,并保存返回值
                      print("装饰器:函数执行完毕")
                      return result  # 返回原始函数的执行结果
                  return wrapper
              
              @decorator_with_args
              def greet(name, message="你好"):
                  print(f"{message}, {name}!")
                  return f"问候了 {name}"
              
              returned_value = greet("Alice", message="早上好")
              print(f"函数返回值: {returned_value}")
              

              输出:

              装饰器:函数开始执行
              早上好, Alice!
              装饰器:函数执行完毕
              函数返回值: 问候了 Alice
              

              6.functools.wraps的重要性

              使用装饰器有一个小问题:它会丢失原始函数的一些元信息(metadata),比如函数名 (__name__)、文档字符串 (__doc__)等。

              @decorator_with_args
              def greet(name):
                  """这是一个打招呼的函数"""
                  print(f"你好, {name}!")
              
              print(greet.__name__)  # 输出: wrapper (而不是 greet)
              print(greet.__doc__)   # 输出: None (而不是 "这是一个打招呼的函数")
              

              这对于调试和自省工具来说非常不便。为了解决这个问题,Python的 functools 模块提供了一个专门的装饰器:@wraps

              import functools
              
              def decorator_for_wraps(func):
                  @functools.wraps(func)  # 关键!将func的元信息拷贝到wrapper上
                  def wrapper(*args, **kwargs):
                      # ... 装饰器逻辑 ...
                      print("装饰器逻辑...")
                   android   return func(*args, **kwargs)
                  return wrapper
              
              @decorator_for_wraps
              def greet(name):
                  """这是一个打招呼的函数"""
                  print(f"你好, {name}!")
              
              print(greet.__name__)  # 输出: greet
              print(greet.__doc__)   # 输出: 这是一个打招呼的函数
              

              最佳实践: 编写任何装饰器时,都应该在你的 wrapper 函数上使用 @functools.wraps

              7. 带参数的装饰器(进阶)

              如果我们想让装饰器本身也接收参数呢?比如 @repeat(num=3),让函数重复执行3次。

              这就需要再加一层函数嵌套。

              结构:

              1. 最外层函数 (repeat):接收装饰器的参数(如 num=3),并返回一个真正的装饰器
              2. 中间层函数 (decorator_repeat):这就是我们之前写的标准装饰器,它接收一个函数作为参数。
              3. 最内层函数 (wrapper):执行增强后的逻辑。
              import functools
              
              def repeat(num=2):  # 1. 最外层函数,接收装饰器参数
                  def decorator_repeat(func):  # 2. 中间层,接收被装饰的函数
                      @functools.wraps(func)
                      def wrapper(*args, **kwargs):  # 3. 最内层,执行逻辑
                          print(f"函数将重复执行 {num} 次。")
                          for _ in range(num):
                              result = func(*args, **kwargs)
                          return result
                      return wrapper
                  return decorator_repeat
              
              @repeat(num=4)
              def say_hello(name):
                  print(f"Hello, {name}!")
              
              say_hello("World")
              

              执行过程:

              1. @repeat(num=4) 首先被调用,它返回 decorator_repeat 函数。
              2. Python接着执行 @decorator_repeat,这等价于 say_hello = decorator_repeat(say_hello)
              3. 最终 say_hello 变量指向了 wrapper 函数。

              8. 类装js饰器(进阶)

              除了用函数,我们也可以用类来创建装饰器。这在需要维护状态时特别有用。一个类要成为装饰器,需要实现 __init____call__ 方法。

              class Counter:
                  def __init__(self, func):
                      functools.update_wrapper(self, func) # 类似 @wraps
                      self.func = func
                      self.num_calls = 0
              
                  def __call__(self, *args, **kwargs):
                      self.num_calls += 1
                      print(f"'{self.func.__name__}' 已被调用 {self.num_calls} 次。")
                      return self.func(*args, **kwargs)
              
              @Counter
              def some_function():
                  print("执行 some_function")
              
              some_function()
              some_function()
              some_function()
              

              输出:

              'some_function' 已被调用 1 次。
              执行 some_function
              'some_function' 已被调用 2 次。
              执行 some_function
              'some_function' 已被调用 3 次。
              执行 some_function
              

              Counter 类的实例 some_function 能够记住自己被调用的次数。

              9. 装饰器的实际应用场景(重点)

              日志记录 (Logging)

              import functools
              import logging
              
              logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
              
              def log_function_call(func):
                  @functools.wraps(func)
                  def wrapper(*args, **kwargs):
                      logging.info(f"Calling function '{func.__name__}' with args={args}, kwargs={kwargs}")
                      result = func(*args, **kwargs)
                      logging.info(f"Function '{func.__name__}' returned {result}")
                      return result
                  return wrapper
              
              @log_function_call
              def add(a, b):
                  return a + b
              
              add(5, 3)
              

              性能计时 (Timing)

              import time
              import functools
              
              def timer(func):
                  @functools.wraps(func)
                  def wrapper(*args, **kwargs):
                      start_time = time.perf_counter()
                      result = func(*args, **kwargs)
                      end_time = time.perf_counter()
                      run_time = end_time - start_time
                      print(f"Function '{func.__name__}' took {run_time:.4f} seconds to complete.")
                      return result
                  return wrapper
              
              @timer
              def process_data():
                  time.sleep(1)
                  print("数据处理完成!")
              
              process_data()
              

              用户认证 (Authentication)

              在Web框架(如Flask, Django)中非常常见。

              # 这是一个简化的概念性例子
              import functools
              
              def login_required(func):
                  @functools.wraps(func)
                  def wrapper(*args, **kwargs):
                      # 伪代码:假设有一个函数 check_user_logged_in()
                      if check_user_logged_in():
                          return func(*args, **kwargs)
                      else:
                          raise PermissionError("用户未登录,禁止访问!")
                  return wrapper
              
              # 模拟一个全局登录状态
              _user_is_logged_in = False
              def check_user_logged_in(): return _user_is_logged_in
              
              @login_required
              def view_profile():
                  print("这是用户的个人资料页面。")
              
              # 尝试调用
              try:
                  view_profile()
              except PermissionError as e:
                  print(e)  # 输出: 用户未登录,禁止访问!
              
              # 模拟用户登录
              _user_is_logged_in = True
              print("\n用户登录后:")
              view_profile() # 输出: 这是用户的个人资料页面。
              

              缓存 (Caching/Memoization)

              对于计算成本高的函数,可以将结果缓存起来。Python 3.9+ 自带了 functools.cache。我们也可以自己实现一个简单的版本。

              import functools
              import time
              
              def memoize(func):
                  cache = {}
                  @functools.wraps(func)
                  def wrapper(*args):
                      if args in cache:
                          return cache[args]
                      result = func(*args)
                      cache[args] = result
                      return result
                  return wrapper
              
              # 使用Python自带的会更高效
              # from functools import lru_cache, cache
              
              @memoize # 或者 @functools.cache
              def fibonacci(n):
                  if n < 2:
                      return n
                  return fibonacci(n - 1) + fibonacci(n - 2)
              
              start = time.time()
              print(fibonacci(35))
              end = time.time()
              print(f"第一次计算耗时: {end - start:.4f}s")
              
              start = time.time()
              print(fibonacci(35))
              end = time.time()
              print(f"第二次(从缓存)计算耗时: {end - start:.4f}s")
              

              输入验证 (Validation)

              在函数执行前检查其参数是否符合要求。

              import functools
              
              def validate_types(*type_args):
                  def decorator(func):
                      @functools.wraps(func)
                      def wrapper(*args, **kwargs):
                          for i, (arg, expected_type) in enumerate(zip(args, type_args)):
                              if not isinstance(arg, expected_type):
                                  raise TypeError(f"Argument {i+1} must be of type {expected_type.__name__}, not {type(arg).__name__}")
                          return func(*args, **kwargs)
                      return wrapper
                  return decorator
              
              @validate_types(int, int)
              def multiply(a, b):
                  return a * b
              
              print(multiply(5, 10))  # 正常工作
              try:
                  multiply(5, "10") # 会抛出 TypeError
              except TypeError as e:
                  print(e)
              

              10. 装饰器栈(多个装饰器)

              一个函数可以被多个装饰器修饰。执行顺序是从下到上(最靠近函数的先被应用),然后执行时是从上到下

              @timer
              @log_function_call
              def complex_calculation(x, y):
                  time.sleep(0.5)
                  return x * y + 2
              

              这等价于:

              complex_calculation = timer(log_function_call(complex_calculation))

              执行 complex_calculation(3, 4) 时:

              1. timerwrapper 开始执行,记录开始时间。
              2. timer 调用 log_function_callwrapper
              3. log_function_callwrapper 开始执行,打印日志 “Calling function…”。
              4. log_function_call 调用原始的 complex_calculation 函数。
              5. complex_calculation 执行,返回结果 14
              6. log_function_callwrapper 拿到结果,打印日志 “…returned 14”。
              7. timerwrapper 拿到结果,记录结束时间,并打印耗时。

              11. 总结

              • 核心:装饰器是一个接收函数并返回新函数的函数,用于在不修改原函数代码的情况下增加功能。
              • 基础:依赖于Python中函数是“一等公民”的特性。
              • 语法糖@decoratormy_func = decorator(my_func) 的简写。
              • 通用性:使用 *args, **kwargs 来处理任意参数的函数。
              • 最佳实践:始终使用 @functools.wraps 来保留原函数的元信息。
              • 灵活性:装饰器可以带参数,也可以用类来实现。
              • 强大应用:日志、计时、认证、缓存、验证等都是装饰器的经典用例,极大地提高了代码的复用性可维护性

              以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程客栈(www.devze.com)。

              0

              上一篇:

              下一篇:

              精彩评论

              暂无评论...
              验证码 换一张
              取 消

              最新开发

              开发排行榜