Python 函数装饰器
什么是函数装饰器?
函数装饰器是Python中的一种强大特性,它允许我们在不修改原函数代码的情况下,给函数添加新的功能。装饰器本质上是一个函数,它接受一个函数作为参数,并返回一个新的函数,新函数通常会包含原函数的调用,并添加一些额外的功能。
装饰器遵循了一个重要的软件设计原则:开放封闭原则,即对扩展开放,对修改封闭。
装饰器就像是一个包装纸,它可以"包装"函数,在不改变原函数的情况下,为其增加新的功能。
基本语法
Python中使用@
符号来应用装饰器,放在要装饰的函数定义之前。
@decorator_function
def target_function():
pass
这等同于:
def target_function():
pass
target_function = decorator_function(target_function)
创建简单的装饰器
让我们创建一个简单的装饰器,它会在函数执行前后打印一些消息:
def my_decorator(func):
def wrapper():
print("在函数执行之前的操作")
func() # 调用原始函数
print("在函数执行之后的操作")
return wrapper
@my_decorator
def say_hello():
print("Hello!")
# 调用被装饰后的函数
say_hello()
输出结果:
在函数执行之前的操作
Hello!
在函数执行之后的操作
装饰器的工作原理解析
my_decorator
函数接受一个函数func
作为参数- 在
my_decorator
内部,定义了一个新函数wrapper
wrapper
函数中执行了一些操作,调用了原始函数func
,然后又执行了一些操作my_decorator
返回了这个新函数wrapper
- 当我们使用
@my_decorator
装饰say_hello
时,Python实际上执行了say_hello = my_decorator(say_hello)
- 之后调用
say_hello()
时,实际上是调用了由装饰器返回的wrapper
函数
处理带有参数的函数
上面的装饰器只适用于不带参数的函数。如果想要装饰带参数的函数,我们需要修改装饰器:
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
带参数的装饰器
有时我们希望装饰器本身也能接受参数,这需要再多一层嵌套:
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!
带参数装饰器的工作原理
repeat(n=1)
调用返回真正的装饰器函数decorator
@repeat(n=3)
实际上相当于@decorator
,其中decorator = repeat(3)
decorator
接受say_hello
函数并返回wrapper
- 当调用
say_hello("Alice")
时,实际上是调用了wrapper("Alice")
保留原函数的元信息
装饰器会替换原函数,这意味着原函数的一些元信息(如名称、文档字符串等)会丢失。我们可以使用Python标准库中的functools.wraps
装饰器来解决这个问题:
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. 计时函数执行时间
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. 日志记录
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. 权限验证
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. 缓存计算结果
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} 秒")
多个装饰器
可以在同一个函数上应用多个装饰器,它们将从上到下依次执行(嵌套顺序是从内到外):
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__
方法:
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!
装饰器最佳实践
- 使用
functools.wraps
保留原函数元信息 - 保持装饰器简单:每个装饰器应该只做一件事
- 合理处理参数:使用
*args, **kwargs
来处理未知参数 - 考虑错误处理:在装饰器中添加适当的错误处理逻辑
- 文档化你的装饰器:为装饰器添加清晰的文档字符串
总结
函数装饰器是Python中一个强大而优雅的特性,它允许我们在不修改现有函数代码的情况下,为函数增加新功能。装饰器广泛应用于日志记录、性能测量、访问控制、缓存等场景。
掌握装饰器需要理解Python中的函数是一等公民这一概念,以及函数嵌套和闭包的知识。通过本教程,你应该已经对装饰器有了基本的了解,并能在自己的代码中使用它们。
练习
- 创建一个装饰器,它会捕获被装饰函数可能抛出的所有异常,并打印异常信息。
- 创建一个带参数的装饰器,该装饰器可以限制函数在指定时间内被调用的最大次数。
- 编写一个装饰器,它可以记录函数的调用历史(参数和返回值)。
- 创建一个可以同时装饰函数和方法的装饰器(提示:处理
self
参数)。
附加资源
祝你的Python学习之旅愉快!