装饰器是 Python 中非常强大且实用的功能之一,它可以帮助我们更加优雅地编写代码。装饰器本质上是一个函数,它可以修改或增强其他函数的行为,而无需改变原函数的代码。今天,我们就来一步步学习 Python 装饰器的基础知识,并通过几个简单的示例来加深理解。
一、基础概念
1.什么是装饰器?
装饰器是一个函数,它接受一个函数作为参数,并返回一个新的函数。这个新的函数通常会在执行原函数之前或之后添加一些额外的功能。
2.装饰器的基本语法
装饰器的基本语法使用 @ 符号。例如:
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.
在这个例子中,my_decorator 是一个装饰器,它接受 say_hello 函数作为参数,并返回一个新的 wrapper 函数。当我们调用 say_hello() 时,实际上是调用了 wrapper() 函数。
二、带参数的装饰器
有时候,我们希望装饰器能够接受参数。这可以通过再嵌套一层函数来实现。
示例:带参数的装饰器
def repeat(num_times):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(num_times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(3)
def greet(name):
print(f"Hello, {name}!")
greet("Alice")
输出:
Hello, Alice!
Hello, Alice!
Hello, Alice!
在这个例子中,repeat 是一个带参数的装饰器,它接受一个整数 num_times 作为参数,并返回一个真正的装饰器 decorator。decorator 再次接受 greet 函数作为参数,并返回 wrapper 函数。wrapper 函数会多次调用 greet 函数。
三、带参数的被装饰函数
如果被装饰的函数本身带有参数,我们需要在 wrapper 函数中传递这些参数。
示例:带参数的被装饰函数
def my_decorator(func):
def wrapper(*args, **kwargs):
print("Something is happening before the function is called.")
result = func(*args, **kwargs)
print("Something is happening after the function is called.")
return result
return wrapper
@my_decorator
def add(a, b):
print(f"Adding {a} and {b}")
return a + b
result = add(3, 5)
print(f"Result: {result}")
输出:
Something is happening before the function is called.
Adding 3 and 5
Something is happening after the function is called.
Result: 8
在这个例子中,add 函数接受两个参数 a 和 b。wrapper 函数通过 *args 和 **kwargs 接受这些参数,并将它们传递给 add 函数。
四、使用 functools.wraps
为了保持被装饰函数的元数据(如名称、文档字符串等),我们可以使用 functools.wraps。
示例:使用 functools.wraps
from functools import wraps
def my_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
print("Something is happening before the function is called.")
result = func(*args, **kwargs)
print("Something is happening after the function is called.")
return result
return wrapper
@my_decorator
def add(a, b):
"""Add two numbers."""
print(f"Adding {a} and {b}")
return a + b
print(add.__name__)
print(add.__doc__)
输出:
add
Add two numbers.
在这个例子中,@wraps(func) 保留了 add 函数的元数据,使得 add.__name__ 和 add.__doc__ 仍然有效。
实战案例:日志记录装饰器
假设我们有一个应用程序,需要记录每个函数的调用时间和返回值。我们可以使用装饰器来实现这一点。
示例:日志记录装饰器
import time
from functools import wraps
def log_function_call(func):
@wraps(func)
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
elapsed_time = end_time - start_time
print(f"Function {func.__name__} was called with arguments {args} and {kwargs}.")
print(f"Return value: {result}")
print(f"Time taken: {elapsed_time:.4f} seconds")
return result
return wrapper
@log_function_call
def compute_sum(a, b):
"""Compute the sum of two numbers."""
time.sleep(1) # Simulate a delay
return a + b
result = compute_sum(3, 5)
输出:
Function compute_sum was called with arguments (3, 5) and {}.
Return value: 8
Time taken: 1.0002 seconds
在这个例子中,log_function_call 装饰器记录了 compute_sum 函数的调用时间、参数和返回值。time.sleep(1) 模拟了一个延迟,以便我们可以看到时间记录的效果。
总结
本文介绍了 Python 装饰器的基础知识,包括装饰器的基本概念、带参数的装饰器、带参数的被装饰函数以及如何使用 functools.wraps 保留元数据。通过四个简单的示例,我们逐步深入理解了装饰器的工作原理和应用场景。最后,我们通过一个实战案例展示了如何使用装饰器来记录函数的调用信息。