Python 结构型模式:装饰器
公众号:人生只不过是一场投资
- 装饰器模式也是结构型模式
- 上一个例子中,朋友反馈理解难度对于他来说较高,所以,这篇文章继续说一说这结构型模式-装饰器
引言
在软件开发中,设计模式是一套被反复使用、经过分类和总结的代码设计经验。装饰器模式(Decorator Pattern)是结构型设计模式之一,用于动态地向对象添加额外的职责,而不影响其他对象。这种模式通过使用装饰器函数,允许你在保持原始功能的基础上,灵活地扩展代码的行为。
应用领域
装饰器模式在以下场景中特别有用:
- 日志记录:在函数执行前后添加日志记录功能。
- 性能测试:测量函数的执行时间。
- 权限验证:在执行函数前进行权限检查。
- 缓存:缓存函数的计算结果,以提高性能。
- 输入校验:在函数执行前对输入参数进行检查。
实例及详解
我们以一个简单的日志记录装饰器为例,来讲解装饰器模式的实现。
基础函数
首先,定义一个基础函数,用于模拟业务逻辑:
def say_hello(name):
print(f"Hello, {name}!")
say_hello("Alice")
在这个例子中,我们定义了一个简单的函数 say_hello
,它接受一个参数 name
,并打印一条问候信息。调用 say_hello("Alice")
会输出 Hello, Alice!
。
装饰器函数
接下来,定义一个装饰器函数,用于在执行 say_hello
函数前后记录日志:
def log_decorator(func):
def wrapper(*args, **kwargs):
print(f"Calling function {func.__name__} with arguments {args} and {kwargs}")
result = func(*args, **kwargs)
print(f"Function {func.__name__} finished executing")
return result
return wrapper
这个装饰器函数 log_decorator
有几点需要注意:
- 接受一个函数作为参数:装饰器函数
log_decorator
接受一个函数func
作为参数。 - 定义一个内部函数
wrapper
:这个内部函数wrapper
实现了装饰功能。在调用原始函数func
前后,添加了日志记录。 - 参数传递:
wrapper
函数使用*args
和**kwargs
传递所有参数,以保证可以接受任意数量的位置参数和关键字参数。 - 调用原始函数:
result = func(*args, **kwargs)
这行代码调用了原始函数,并将结果存储在result
变量中。 - 返回结果:
wrapper
函数返回原始函数的结果。 - 返回
wrapper
函数:log_decorator
最后返回wrapper
函数,从而完成对原始函数的装饰。
应用装饰器
使用装饰器语法糖 @
来应用装饰器函数:
@log_decorator
def say_hello(name):
print(f"Hello, {name}!")
say_hello("Alice")
通过在 say_hello
函数定义之前添加 @log_decorator
,我们将 say_hello
函数传递给 log_decorator
,并用 log_decorator
返回的 wrapper
函数替换 say_hello
。这意味着,每次调用 say_hello
实际上是在调用 wrapper
函数。
运行上述代码,输出如下:
Calling function say_hello with arguments ('Alice',) and {}
Hello, Alice!
Function say_hello finished executing
从输出中可以看到,装饰器成功地在调用 say_hello
函数前后记录了日志。
详细说明
- 调用函数前日志:在执行原始函数前,
wrapper
打印了一条日志,显示函数名称和传递的参数。 - 调用原始函数:
wrapper
调用了原始的say_hello
函数,输出Hello, Alice!
。 - 调用函数后日志:在原始函数执行完毕后,
wrapper
打印了一条日志,表示函数执行结束。
优点
- 增强功能:无需修改原始函数代码,即可为其添加新功能。
- 保持单一职责:通过分离功能,保持每个函数职责单一,有利于代码的维护和扩展。
- 复用性高:一个装饰器可以应用于多个不同的函数,提高代码的复用性。
缺点
- 调试复杂:过多的装饰器嵌套会增加调试难度,难以追踪函数的执行路径。
- 性能开销:由于每个装饰器都会引入额外的函数调用,可能会带来性能上的开销。
- 可读性下降:对于不熟悉装饰器模式的开发者来说,理解代码可能会有一定的困难。
结论
装饰器模式是Python中强大而灵活的设计模式,广泛应用于各种场景中。通过使用装饰器,我们可以在不修改现有代码的基础上,为函数动态添加新功能。尽管有一些潜在的缺点,如调试复杂和性能开销,但合理使用装饰器能够显著提高代码的可维护性和扩展性。在实际开发中,理解并熟练运用装饰器模式,将帮助你编写出更简洁、模块化和可扩展的代码。
日志记录应用
日志配置
首先,我们需要配置 logging
库,将日志记录到一个 log.txt
文件中:
import logging
# 配置日志
logging.basicConfig(level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
filename='log.txt',
filemode='a')
logger = logging.getLogger(__name__)
定义装饰器
定义一个装饰器函数,用于在函数执行前后记录日志:
def log_decorator(func):
def wrapper(*args, **kwargs):
logger.info(f"Calling function {func.__name__} with arguments {args} and {kwargs}")
result = func(*args, **kwargs)
logger.info(f"Function {func.__name__} finished executing")
return result
return wrapper
示例一:函数使用装饰器
使用装饰器语法糖 @
来应用装饰器函数:
@log_decorator
def say_hello(name):
print(f"Hello, {name}!")
say_hello("Alice")
运行结果
运行上述代码,控制台输出:
Hello, Alice!
同时,log.txt
文件中记录了以下日志:
2024-05-19 18:56:40,998 - __main__ - INFO - Calling function say_hello with arguments ('Alice',) and {}
2024-05-19 18:56:40,998 - __main__ - INFO - Function say_hello finished executing
示例二:类使用装饰器
在这个示例中,我们将为一个类的方法添加日志记录功能。
定义类并应用装饰器
定义一个类,并使用装饰器记录方法的日志:
class Greeter:
@log_decorator
def greet(self, name):
print(f"Hello, {name}!")
greeter = Greeter()
greeter.greet("Bob")
运行结果
运行上述代码,控制台输出:
Hello, Bob!
同时,log.txt
文件中记录了以下日志:
2024-05-19 18:58:07,830 - __main__ - INFO - Calling function greet with arguments (<__main__.Greeter object at 0x000001C7991944D0>, 'Bob') and {}
2024-05-19 18:58:07,830 - __main__ - INFO - Function greet finished executing
详细说明
- 日志配置:我们使用
logging.basicConfig
配置日志记录。日志级别设置为INFO
,日志消息格式包括时间戳、记录器名称、日志级别和消息内容。日志文件名为log.txt
,以追加模式打开。 - 装饰器函数:
log_decorator
接受一个函数或方法作为参数,并返回一个包装函数wrapper
。在wrapper
中,记录函数或方法调用的日志,并在执行完毕后记录日志。 - 函数示例:我们定义了一个简单的函数
say_hello
,并使用装饰器log_decorator
为其添加日志记录功能。调用say_hello("Alice")
会在控制台输出问候信息,并在log.txt
文件中记录函数调用的日志。 - 类示例:我们定义了一个类
Greeter
,其方法greet
使用装饰器log_decorator
添加日志记录功能。调用greeter.greet("Bob")
会在控制台输出问候信息,并在log.txt
文件中记录方法调用的日志。
通过以上两个示例,展示了如何使用 logging
库和装饰器模式来记录日志,并将日志保存到 txt
文件中。这种方法提高了代码的可维护性和功能扩展性。
性能测试与日志记录场景应用
定义装饰器
定义一个装饰器函数,用于在函数执行前后测量并记录执行时间:
import time
import logging
# 配置日志
logging.basicConfig(level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
filename='performance_log.txt',
filemode='a')
logger = logging.getLogger(__name__)
def time_decorator(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
execution_time = end_time - start_time
logger.info(f"Function {func.__name__} executed in {execution_time:.4f} seconds")
return result
return wrapper
示例一:函数使用装饰器
使用装饰器语法糖 @
来应用装饰器函数:
@time_decorator
def say_hello(name):
time.sleep(1) # 模拟耗时操作
print(f"Hello, {name}!")
say_hello("Alice")
运行结果
运行上述代码,控制台输出:
Hello, Alice!
同时,performance_log.txt
文件中记录了以下日志:
2024-05-19 19:10:30,359 - __main__ - INFO - Function say_hello executed in 1.0008 seconds
示例二:类使用装饰器
定义一个类,并使用装饰器测量方法的执行时间:
class Greeter:
@time_decorator
def greet(self, name):
time.sleep(2) # 模拟耗时操作
print(f"Hello, {name}!")
greeter = Greeter()
greeter.greet("Bob")
运行结果
运行上述代码,控制台输出:
Hello, Bob!
同时,performance_log.txt
文件中记录了以下日志:
2024-05-19 19:11:20,958 - __main__ - INFO - Function greet executed in 2.0008 seconds
详细说明
- 日志配置:我们使用
logging.basicConfig
配置日志记录。日志级别设置为INFO
,日志消息格式包括时间戳、记录器名称、日志级别和消息内容。日志文件名为performance_log.txt
,以追加模式打开。 - 装饰器函数:
time_decorator
接受一个函数或方法作为参数,并返回一个包装函数wrapper
。在wrapper
中,记录函数或方法的开始时间和结束时间,并计算执行时间。 - 函数示例:我们定义了一个简单的函数
say_hello
,并使用装饰器time_decorator
为其添加执行时间测量功能。调用say_hello("Alice")
会在控制台输出问候信息,并在performance_log.txt
文件中记录函数执行的时间。 - 类示例:我们定义了一个类
Greeter
,其方法greet
使用装饰器time_decorator
添加执行时间测量功能。调用greeter.greet("Bob")
会在控制台输出问候信息,并在performance_log.txt
文件中记录方法执行的时间。
通过以上两个示例,展示了如何使用 time
、logging
模块和装饰器模式来测量并记录函数和方法的执行时间,并将这些性能数据保存到 txt
文件中。这种方法提高了代码的可维护性和功能扩展性,并且能够有效地进行性能分析。