今天,我们来探索 Python 装饰器的强大功能,学习如何通过使用装饰器来减少代码量,提高效率与可读性。
什么是装饰器
装饰器(decorator)是 Python 中一种强大的特性,它允许你改变函数或类的行为,而无需修改其源代码。装饰器本质上是一个函数,它接收另一个函数作为参数,并返回一个新的函数,这个新函数会包裹原始函数。通过这种方式,你可以在不修改原始函数的情况下,为其添加一些额外的功能或逻辑。
例如,假设你有一个函数,用于在控制台打印一条消息:
def hello():
print("Hello, world!")
现在,假设你想测量这个函数的执行时间。你可能会这样做:
import time
def hello():
start = time.time()
print("Hello, world!")
end = time.time()
print(f"执行时间: {end - start} 秒")
但考虑到测量执行时间的功能很可能会复用,我们最好将它独立封装成一个函数,使用 time 模块来计算执行时间,然后调用原始函数:
import time
def measure_time(func):
def wrapper():
start = time.time()
func()
end = time.time()
print(f"执行时间: {end - start} 秒")
return wrapper
注意,measure_time 函数返回了另一个名为wrapper的函数,这是原始函数的修改版本。wrapper 函数做了两件事:它记录了执行的开始和结束时间,并调用了原始函数。
现在,要使用这个函数,只需要:
hello = measure_time(hello)
hello()
这将输出类似于这样的内容:
Hello, world!
执行时间: 0.000123456789 秒
通过这种方式,你无需修改hello函数的代码,就为其添加了测量执行时间的新功能。然而,有一种更优雅和简洁的方法来实现这一点,那就是使用装饰器。装饰器是一种语法糖,允许你使用@符号将一个函数应用到另一个函数上。例如,我们可以像这样重写前面的代码:
@measure_time
def hello():
print("Hello, world!")
hello()
这将产生与之前相同的输出,但代码量大大减少。@measure_time这一行等同于hello = measure_time(hello),但它看起来更清晰,更易于阅读。
为什么要用装饰器
使用装饰器的原因有很多,例如:
代码可复用。例如,如果你有许多需要测量执行时间的函数,你可以简单地为它们都应用相同的装饰器,而不是一遍又一遍地编写相同的代码。
遵循单一职责的原则。例如,如果你有一个执行复杂计算功能的函数,你可以使用装饰器来处理日志记录、错误处理、缓存或验证输入和输出,而不必弄乱函数的主要逻辑。
扩展功能的同时无需修改源代码。例如,如果你正在使用一个第三方库的类或函数,但你想为它们添加一些额外的功能或行为,你可以使用装饰器来包装它们,并根据你的需要进行定制。
装饰器示例
Python 中有许多内置装饰器,如@staticmethod、@classmethod、@property、@functools.lru_cache、@functools.singledispatch等。你也可以创建自己的自定义装饰器来满足各种需求。以下是一些可以有效精简代码的自定义装饰器示例:
@timer装饰器
这个装饰器与我们上面看到的@measure_time装饰器类似,但可以应用于任意数量参数的函数。它还使用functools.wraps装饰器来保留原始函数的名称和文档字符串(docstring)。代码如下:
import time
from functools import wraps
def timer(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__}的执行时间: {end - start} 秒")
return result
return wrapper
现在,你可以使用这个装饰器来测量任何函数的执行时间,例如:
@timer
def factorial(n):
"""返回n的阶乘"""
if n == 0 or n == 1:
return 1
else:
return n * factorial(n - 1)
@timer
def fibonacci(n):
"""返回第n个斐波那契数"""
if n == 0 or n == 1:
return n
else:
return fibonacci(n - 1) + fibonacci(n - 2)
print(factorial(10))
print(fibonacci(10))
这将输出类似于这样的内容:
factorial的执行时间: 1.1920928955078125e-06 秒
3628800
fibonacci的执行时间: 0.000123456789 秒
55
通过这种方式,@timer装饰器为factorial和fibonacci函数添加了测量执行时间的新功能。
@debug装饰器
这个装饰器可用于调试目的,它支持输出它所装饰的函数的名称、参数和返回值。并使用functools.wraps装饰器来保留原始函数的名称和文档字符串。代码如下:
from functools import
def debug(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"调用 {func.__name__},参数: {args},关键字参数: {kwargs}")
result = func(*args, **kwargs)
print(f"{func.__name__} 返回: {result}")
return result
return wrapper
现在,你可以使用这个装饰器来调试任何函数,例如:
@debug
def add(x, y):
"""返回x和y的和"""
return x + y
@debug
def greet(name, message="Hello"):
"""返回带有名字的问候语"""
return f"{message}, {name}!"
print(add(2, 3))
print(greet("Alice"))
print(greet("Bob", message="Hi"))
这将输出类似于这样的内容:
调用 add,参数: (2, 3),关键字参数: {}
add 返回: 5
5
调用 greet,参数: ('Alice',),关键字参数: {}
greet 返回: Hello, Alice!
Hello, Alice!
调用 greet,参数: ('Bob',),关键字参数: {'message': 'Hi'}
greet 返回: Hi, Bob!
Hi, Bob!
通过这种方式,@debug装饰器为add和greet函数添加了调试功能,能够清晰地显示函数调用时的参数和返回值。
@memoize装饰器
这个装饰器对于优化函数性能非常有用(尤其是用了递归、或是需要大量计算的函数),因为它缓存了之前调用的结果,并在再次传递相同的参数时返回这些结果。同样使用了functools.wraps装饰器来保留原始函数的名称和文档字符串。代码如下:
from functools import wraps
def memoize(func):
cache = {}
@wraps(func)
def wrapper(*args):
if args in cache:
return cache[args]
else:
result = func(*args)
cache[args] = result
return result
return wrapper
现在,你可以使用这个装饰器来缓存任何函数的结果,例如:
@memoize
def factorial(n):
"""返回n的阶乘"""
if n == 0 or n == 1:
return 1
else:
return n * factorial(n - 1)
@memoize
def fibonacci(n):
"""返回第n个斐波那契数"""
if n == 0 or n == 1:
return n
else:
return fibonacci(n - 1) + fibonacci(n - 2)
print(factorial(10))
print(fibonacci(10))
这将输出与之前相同的结果,但执行时间会快得多,因为执行结果做了缓存。
通过这种方式,@memoize装饰器为factorial和fibonacci函数添加了缓存结果的功能,大大提高了这些函数的执行效率。
结语
Python 装饰器是一种强大而优雅的特性,能够在不修改源代码的前提下,改变函数或类的行为。它们可以帮助你减少代码量,提高代码的可读性,复用性,降低耦合性,以及扩展现有代码的功能。
扫码二维码 获取免费视频学习资料
- 本文固定链接: http://phpxs.com/post/11926/
- 转载请注明:转载必须在正文中标注并保留原文链接
- 扫码: 扫上方二维码获取免费视频资料
查 看2022高级编程视频教程免费获取