装饰器在Python中是一种非常强大的功能,它允许修改和增强函数或方法的行为,而不需要改变其本身的定义。
1. 装饰器基础
在深入理解装饰器之前,首先需要了解Python中函数也是对象,这意味着它们可以被赋值给变量、作为参数传递给其他函数,或者在其他函数内部定义。
函数作为对象
def greet(name):
return f"Hello, {name}!"
greet_someone = greet
print(greet_someone("World"))
将函数greet
赋值给变量greet_someone
,之后通过新的变量名调用函数。输出结果是:“Hello, World!”。
在函数中定义函数
可以在一个函数内部定义另一个函数:
def speak(text):
def whisper(t):
return t.lower() + "..."
return whisper(text)
print(speak("Hello World"))
输出结果是:“hello world…”。whisper
是在speak
内部定义并且只能在speak
内部使用的函数。
函数作为参数
函数因为是对象,所以也可以作为参数传递给其他函数。
def greet(name):
return f"Hello, {name}!"
def call_func(func):
other_name = "Alice"
return func(other_name)
print(call_func(greet))
这段代码将函数greet
作为参数传递给call_func
函数,输出结果是:“Hello, Alice!”。
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
def say_hello():
print("Hello!")
say_hello = my_decorator(say_hello)
say_hello()
# Something is happening before the function is called.
# Hello!
# Something is happening after the function is called.
my_decorator
是一个装饰器,它内部定义了一个wrapper
函数,该函数将被用来包裹原始函数say_hello
以增加额外的功能(在函数执行前后打印消息)。然后通过say_hello = my_decorator(say_hello)
应用装饰器。
使用@
符号应用装饰器
Python提供了一个简便的方法来应用装饰器,即使用@
符号:
@my_decorator
def say_hello():
print("Hello!")
say_hello()
跟之前的例子效果完全相同,但代码更加简洁、易读。@my_decorator
语法是Python提供的一种便捷方式,可以轻松地将装饰器应用到函数上,注意这里的@my_decorator
是上一个例子定义的函数。
3. 装饰器进阶
装饰器不仅限于无参数的函数,它们也可以装饰接收参数的函数。此外,装饰器本身也可以接收参数。
3.1 装饰带参数的函数
当装饰的函数需要接收参数时,需要在内部wrapper
函数中正确处理这些参数
def trace(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__} with args {args} and kwargs {kwargs}")
result = func(*args, **kwargs)
print(f"{func.__name__} returned {result}")
return result
return wrapper
@trace
def calculate_area(length, width):
return length * width
area = calculate_area(10, 20)
print(f"Area: {area}")
# Calling calculate_area with args (10, 20) and kwargs {}
# calculate_area returned 200
# Area: 200
trace
装饰器被定义为监控任何函数的调用和返回值。装饰器内部的wrapper
函数记录了传递给calculate_area
函数的参数(length
和width
)以及该函数的返回结果(计算的面积)。
装饰器接收函数参数的方式不限于使用 *args 和 **kwargs,这只是一种通用的方法而已,可以使得装饰器能够适用于任意参数的函数。
3.2 带参数的装饰器
有时我们希望装饰器本身能够接收参数。创建一个带参数的装饰器,该装饰器允许指定函数被调用的次数。
def repeat(num_times):
"""装饰器工厂,接收一个参数指定函数重复执行的次数。"""
def decorator_repeat(func):
"""实际的装饰器,用于包装函数,使其可以重复执行指定次数。"""
def wrapper(*args, **kwargs):
"""包装函数,执行被装饰函数多次,并在最后一次保留返回结果。"""
result = None
for _ in range(num_times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator_repeat
@repeat(num_times=2)
def greet(name):
"""一个简单的问候函数,打印问候语并不返回任何值。"""
print(f"Hello, {name}!")
greet("Alice")
# Hello, Alice!
# Hello, Alice!
- 装饰器工厂:
repeat
函数接收一个参数num_times
,这个参数指定了被装饰的函数需要重复执行的次数。这使得repeat
不仅仅是一个装饰器,而是一个装饰器工厂,它根据传入的参数创建并返回一个具体的装饰器。 - 实际的装饰器:
decorator_repeat
是在repeat
函数内部定义的函数,是真正的装饰器。它的任务是接收一个函数并返回一个包装过的版本。 - 包装函数:
wrapper
函数是在decorator_repeat
内部定义的。这个函数负责实际调用原始函数(在本例中为greet
函数),并根据num_times
的值重复执行它。
补充:
解释一下for _ in range(num_times):
中的_
**_
**是一个常用于循环中的占位符。当在循环中不需要使用到循环变量时,可以使用下划线(_
)作为一个临时或者“不关心”的变量名。这是一个惯用法,表示循环次数的变量在循环体中没有被实际用到。
4. 使用装饰器的好处
- 增加代码的复用性:通过装饰器,可以将某些功能独立出来,应用到多个函数上。
- 增强函数功能:装饰器可以在不修改原始函数代码的情况下,增加额外的功能,如日志记录、权限检查等。
- 简化代码结构:装饰器帮助我们将功能分离,避免每个函数都重复相同的代码,使得代码更加简洁和易于维护。
推荐我的相关专栏: python 错误记录