跳到主要内容

Python 函数装饰器

什么是函数装饰器?

函数装饰器是Python中的一种强大特性,它允许我们在不修改原函数代码的情况下,给函数添加新的功能。装饰器本质上是一个函数,它接受一个函数作为参数,并返回一个新的函数,新函数通常会包含原函数的调用,并添加一些额外的功能。

装饰器遵循了一个重要的软件设计原则:开放封闭原则,即对扩展开放,对修改封闭。

装饰器的核心思想

装饰器就像是一个包装纸,它可以"包装"函数,在不改变原函数的情况下,为其增加新的功能。

基本语法

Python中使用@符号来应用装饰器,放在要装饰的函数定义之前。

python
@decorator_function
def target_function():
pass

这等同于:

python
def target_function():
pass

target_function = decorator_function(target_function)

创建简单的装饰器

让我们创建一个简单的装饰器,它会在函数执行前后打印一些消息:

python
def my_decorator(func):
def wrapper():
print("在函数执行之前的操作")
func() # 调用原始函数
print("在函数执行之后的操作")
return wrapper

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

# 调用被装饰后的函数
say_hello()

输出结果:

在函数执行之前的操作
Hello!
在函数执行之后的操作

装饰器的工作原理解析

  1. my_decorator函数接受一个函数func作为参数
  2. my_decorator内部,定义了一个新函数wrapper
  3. wrapper函数中执行了一些操作,调用了原始函数func,然后又执行了一些操作
  4. my_decorator返回了这个新函数wrapper
  5. 当我们使用@my_decorator装饰say_hello时,Python实际上执行了say_hello = my_decorator(say_hello)
  6. 之后调用say_hello()时,实际上是调用了由装饰器返回的wrapper函数

处理带有参数的函数

上面的装饰器只适用于不带参数的函数。如果想要装饰带参数的函数,我们需要修改装饰器:

python
def my_decorator(func):
def wrapper(*args, **kwargs): # 使用*args, **kwargs接收任意参数
print("在函数执行之前的操作")
result = func(*args, **kwargs) # 调用原始函数并传递参数
print("在函数执行之后的操作")
return result # 返回原始函数的结果
return wrapper

@my_decorator
def add(a, b):
print(f"计算 {a} + {b}")
return a + b

# 调用被装饰后的函数
result = add(3, 5)
print(f"结果: {result}")

输出结果:

在函数执行之前的操作
计算 3 + 5
在函数执行之后的操作
结果: 8

带参数的装饰器

有时我们希望装饰器本身也能接受参数,这需要再多一层嵌套:

python
def repeat(n=1):
def decorator(func):
def wrapper(*args, **kwargs):
result = None
for _ in range(n):
result = func(*args, **kwargs)
return result
return wrapper
return decorator

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

say_hello("Alice")

输出结果:

Hello, Alice!
Hello, Alice!
Hello, Alice!

带参数装饰器的工作原理

  1. repeat(n=1)调用返回真正的装饰器函数decorator
  2. @repeat(n=3)实际上相当于@decorator,其中decorator = repeat(3)
  3. decorator接受say_hello函数并返回wrapper
  4. 当调用say_hello("Alice")时,实际上是调用了wrapper("Alice")

保留原函数的元信息

装饰器会替换原函数,这意味着原函数的一些元信息(如名称、文档字符串等)会丢失。我们可以使用Python标准库中的functools.wraps装饰器来解决这个问题:

python
import functools

def my_decorator(func):
@functools.wraps(func) # 保留原函数的元信息
def wrapper(*args, **kwargs):
print("在函数执行之前的操作")
result = func(*args, **kwargs)
print("在函数执行之后的操作")
return result
return wrapper

@my_decorator
def add(a, b):
"""返回两个数的和"""
return a + b

# 现在我们可以访问原函数的元信息
print(add.__name__) # 输出: add
print(add.__doc__) # 输出: 返回两个数的和

实际应用场景

装饰器在Python中有许多实际应用,以下是一些常见例子:

1. 计时函数执行时间

python
import time
import functools

def timer(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"{func.__name__} 执行耗时: {end_time - start_time:.4f} 秒")
return result
return wrapper

@timer
def slow_function():
"""一个耗时的函数"""
time.sleep(2)
return "操作完成"

slow_function() # 输出: slow_function 执行耗时: 2.0013 秒

2. 日志记录

python
import functools
import logging

logging.basicConfig(level=logging.INFO)

def log_execution(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
logging.info(f"调用 {func.__name__} 函数,参数: {args}, {kwargs}")
result = func(*args, **kwargs)
logging.info(f"{func.__name__} 函数返回: {result}")
return result
return wrapper

@log_execution
def divide(a, b):
return a / b

divide(10, 2) # 会记录函数调用和返回值

3. 权限验证

python
import functools

def require_auth(func):
@functools.wraps(func)
def wrapper(user, *args, **kwargs):
if not user.is_authenticated:
raise PermissionError("需要登录才能执行此操作")
return func(user, *args, **kwargs)
return wrapper

# 模拟用户类
class User:
def __init__(self, authenticated=False):
self.is_authenticated = authenticated

@require_auth
def view_protected_resource(user):
return "这是受保护的资源"

# 使用
authenticated_user = User(authenticated=True)
unauthenticated_user = User(authenticated=False)

print(view_protected_resource(authenticated_user)) # 正常访问
try:
print(view_protected_resource(unauthenticated_user))
except PermissionError as e:
print(f"错误: {e}") # 输出: 错误: 需要登录才能执行此操作

4. 缓存计算结果

python
import functools

def memoize(func):
cache = {}
@functools.wraps(func)
def wrapper(*args):
if args not in cache:
cache[args] = func(*args)
return cache[args]
return wrapper

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

# 测试性能
import time
start = time.time()
print(fibonacci(35)) # 不使用缓存会很慢,使用缓存后快很多
print(f"耗时: {time.time() - start:.4f} 秒")

多个装饰器

可以在同一个函数上应用多个装饰器,它们将从上到下依次执行(嵌套顺序是从内到外):

python
def decorator1(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print("装饰器1开始")
result = func(*args, **kwargs)
print("装饰器1结束")
return result
return wrapper

def decorator2(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print("装饰器2开始")
result = func(*args, **kwargs)
print("装饰器2结束")
return result
return wrapper

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

say_hello()

输出结果:

装饰器1开始
装饰器2开始
Hello!
装饰器2结束
装饰器1结束
多装饰器执行顺序

多个装饰器的执行顺序是从内到外的,即最靠近函数定义的装饰器先执行,然后依次向外。上面的例子中,@decorator2先执行,然后是@decorator1

类装饰器

除了函数,我们也可以使用类作为装饰器。类装饰器需要实现__call__方法:

python
class CountCalls:
def __init__(self, func):
functools.update_wrapper(self, func)
self.func = func
self.count = 0

def __call__(self, *args, **kwargs):
self.count += 1
print(f"{self.func.__name__} 已被调用 {self.count} 次")
return self.func(*args, **kwargs)

@CountCalls
def say_hello(name):
return f"Hello, {name}!"

print(say_hello("World"))
print(say_hello("Python"))

输出结果:

say_hello 已被调用 1 次
Hello, World!
say_hello 已被调用 2 次
Hello, Python!

装饰器最佳实践

  1. 使用functools.wraps保留原函数元信息
  2. 保持装饰器简单:每个装饰器应该只做一件事
  3. 合理处理参数:使用*args, **kwargs来处理未知参数
  4. 考虑错误处理:在装饰器中添加适当的错误处理逻辑
  5. 文档化你的装饰器:为装饰器添加清晰的文档字符串

总结

函数装饰器是Python中一个强大而优雅的特性,它允许我们在不修改现有函数代码的情况下,为函数增加新功能。装饰器广泛应用于日志记录、性能测量、访问控制、缓存等场景。

掌握装饰器需要理解Python中的函数是一等公民这一概念,以及函数嵌套和闭包的知识。通过本教程,你应该已经对装饰器有了基本的了解,并能在自己的代码中使用它们。

练习

  1. 创建一个装饰器,它会捕获被装饰函数可能抛出的所有异常,并打印异常信息。
  2. 创建一个带参数的装饰器,该装饰器可以限制函数在指定时间内被调用的最大次数。
  3. 编写一个装饰器,它可以记录函数的调用历史(参数和返回值)。
  4. 创建一个可以同时装饰函数和方法的装饰器(提示:处理self参数)。

附加资源

祝你的Python学习之旅愉快!