python 装饰器从入门到精通-2
本文将介绍5个实用的装饰器,实现日志、计算函数执行时间、返回值类型检查、函数参数验证以及异常处理功能。无论您是想分析性能、提高效率、验证数据还是管理错误,这些装饰器都可以满足您的需求!选择的示例侧重于典型应用模式和实用性,它们可以在您的日常编程中派上用场,并为您节省大量工作。理解装饰器的灵活性将帮助您编写干净、有弹性和优化的应用程序代码。
Python装饰器为修改和扩展函数的行为提供了简单而强大的语法。装饰器(decorator)本质上是函数,它接受函数作为参数、增强其功能,并返回新的函数——而不需要永久修改原始函数本身。
上文我们讨论了装饰器的原理,本文将介绍5个实用的装饰器,实现日志、计算函数执行时间、返回值类型检查、函数参数验证以及异常处理功能。无论您是想分析性能、提高效率、验证数据还是管理错误,这些装饰器都可以满足您的需求!选择的示例侧重于典型应用模式和实用性,它们可以在您的日常编程中派上用场,并为您节省大量工作。理解装饰器的灵活性将帮助您编写干净、有弹性和优化的应用程序代码。
python 装饰器原理回顾
在继续阅读之前,让我们先了解Python中装饰器的主要优势:
- 无需进行侵入性更改即可增强函数:装饰器可以透明地增强函数,无需更改原始代码,保持核心逻辑清晰和可维护。
- 通用功能装饰器:日志记录、缓存和速率限制等常见功能,仅在decorator中实现依次,并可在需要的地方重复应用。
- 可读性和声明性语法:@decorator语法只是传达了定义位置的功能增强。
- 模块化和关注点分离:装饰器实现函数逻辑和次要功能(如性能、安全性、日志记录等)之间的松耦合。
因此,装饰器提供了简单而灵活的方式,可以透明地增强Python函数,以改进代码组织、实现效率和功能重用,而不会引入复杂性或冗余。
下面是带有注解的Python装饰器语法的基本示例:
# Decorator function
def my_decorator(func):
# Wrapper function
def wrapper():
print("Before the function call") # Extra processing before the function
func() # Call the actual function being decorated
print("After the function call") # Extra processing after the function
return wrapper # Return the nested wrapper function
# Function to decorate
def my_function():
print("Inside my function")
# Apply decorator on the function
@my_decorator
def my_function():
print("Inside my function")
# Call the decorated function
my_function()
Python中的装饰器是函数,它将另一个函数作为参数,并在不修改它的情况下扩展其行为。decorator函数通过在原始函数内部定义包装器函数来包装原始函数,这个包装器函数在调用原始函数之前和之后执行增强逻辑代码。
具体来说,当定义一个decorator函数(如示例中的my_decorator)时,它接受一个函数作为参数,我们通常称之为func,这个函数将是在底层装饰的实际函数。
my_decorator中的包装器函数可以在调用func()
之前和之后执行任意代码,当然也会调用原始函数。当在my_function的定义之前应用@my_decorator时,它将my_function作为参数传递给my_decorator,因此func在该上下文中引用my_function。
然后包装器函数返回增强的包装函数,现在my_function已经被my_decorator装饰过了。当稍后调用它时,my_decorator中的包装器代码在my_function运行之前和之后执行。这使得装饰器可以透明地扩展函数的行为,而无需修改函数本身。
我们看到,最初的my_function保持不变,使decorator保持非侵入性和灵活性。当my_function()用@my_decorator装饰时,它会自动增强。这里的my_decorator函数返回包装器函数,现在当调用my_function()时,这个包装器函数会执行。
首先,包装器在实际调用被修饰的原始my_function()函数之前打印“Before the function call”。然后,在my_function()执行之后,它打印“after function call”。
因此,在包装器中的my_function()执行之前和之后添加额外的行为和打印的消息,而不直接修改my_function()函数本身。装饰器允许以透明的方式扩展my_function(),而不会影响其核心逻辑,因为包装器处理增强的行为逻辑。
图1:
将装饰器应用于函数
回顾了包装器基本原理之后,让我们开始探索每个Python开发人员都应该知道的11个最实用的装饰器。
日志包装器
日志包装器可以跟踪输入参数、输出结果。通常用于调试、业务监控、性能分析等。请看下面示例及说明。
def log_decorator(original_function):
def wrapper(*args, **kwargs):
print(f"Calling {original_function.__name__} with args: {args}, kwargs: {kwargs}")
# Call the original function
result = original_function(*args, **kwargs)
# Log the return value
print(f"{original_function.__name__} returned: {result}")
# Return the result
return result
return wrapper
# Example usage
@log_decorator
def calculate_product(x, y):
return x * y
# Call the decorated function
result = calculate_product(10, 20)
print("Result:", result)
输出结果:
Calling calculate_product with args: (10, 20), kwargs: {}
calculate_product returned: 200
Result: 200
在本例中,decorator函数名为log_decorator(),并接受函数original_function作为其参数。在log_decorator()中,定义了名为wrapper()的嵌套函数。这个wrapper()函数是装饰器返回的,它有效地替代了原始函数。当调用wrapper()函数时,它打印与函数调用相关的日志语句。然后调用原始函数original_function,捕获其结果,打印结果,并返回结果。
calculate_product()函数上面的@log_decorator语法是Python约定,用于将log_decorator作为decorator应用于calculate_product函数。因此,当调用calculate_product()时,它实际上调用了log_decorator()返回的wrapper()函数。因此,log_decorator()充当包装器,在执行原始calculate_product()函数之前和之后引入日志记录语句。
- 应用场景
日志装饰器在应用程序开发中被广泛采用,用于添加运行时日志记录,而不会干扰业务逻辑实现。例如,考虑一个处理金融事务的银行应用程序。核心事务处理逻辑驻留在transfer_funds()和accept_payment()等函数中。为了监视这些事务,可以通过在每个函数上面包含@log_decorator来添加日志记录。
然后,当调用transfer_funds()触发交易时,可以在实际转账之前打印函数名、发送方、接收方等参数和金额。然后在函数返回后,您可以打印传输是否成功或失败。这种带有装饰器的日志类型允许跟踪交易,而无需向transfer_funds()等核心函数添加任何代码。在可调试性和可观察性提高的同时,保持逻辑干净。日志记录消息也可以发送到监视仪表大屏或日志分析系统。
计时包装器
这个装饰器是实现性能优化的利器。通过测量和记录函数的执行时间,有助于深入了解代码的效率,帮助确定瓶颈并提升应用程序的性能。它非常适合对性能要求至关重要的场景,例如实时应用程序或大规模数据处理,支持系统地识别和解决性能瓶颈。
import time
def measure_execution_time(func):
def timed_execution(*args, **kwargs):
start_timestamp = time.time()
result = func(*args, **kwargs)
end_timestamp = time.time()
execution_duration = end_timestamp - start_timestamp
print(f"Function {func.__name__} took {execution_duration:.2f} seconds to execute")
return result
return timed_execution
# Example usage
@measure_execution_time
def multiply_numbers(numbers):
product = 1
for num in numbers:
product *= num
return product
# Call the decorated function
result = multiply_numbers([i for i in range(1, 10)])
print(f"Result: {result}")
输出结果:
Function multiply_numbers took 0.00 seconds to execute
Result: 362880
这段代码展示了用于度量函数执行时间的装饰器。
measure_execution_time()装饰器接受函数func,并定义内部函数timed_execution()来包装原始函数。在调用时,timed_execution()记录开始时间,调用原始函数,记录结束时间,计算持续时间,并打印出来。
@measure_execution_time语法将这个装饰器应用于它下面的函数,比如multiply_numbers()。因此,当调用multiply_numbers()时,它调用timed_execution()包装器,该包装器将持续时间与函数结果一起记录下来。
这个例子说明装饰器如何在不直接修改原始代码情况下,用额外的功能(比如计时)无缝地扩展现有函数。
- 应用场景
该装饰器有助于分析函数以识别应用程序中的性能瓶颈。例如,考虑电子商务网站场景,它有几个后端函数,如get_recommendations()、calculate_shipping()等。通过使用@measure_execution_time修饰它们,可以监视它们的运行时性能。
当在用户会话中调用get_recommendations()时,装饰器将通过记录开始和结束时间戳来计算其执行时间。执行后,它将打印函数执行所花费的时间。
系统地跨应用程序执行此操作,并分析花费异常时间的函数调用。然后,开发团队可以通过缓存、并行处理和其他技术来优化这些功能,以提高应用程序的整体性能。如果没有这样的计时装饰器,寻找瓶颈函数逻辑将需要添加繁琐的日志代码。装饰器很容易提供可见性,而不会污染业务逻辑。
返回值类型转换
该装饰器通过自动将返回值转换为指定的数据类型来增强函数中的数据一致性,从而提高可预测性并防止意外错误。它对于需要一致数据类型的下游进程特别有用,可以减少运行时错误。
def convert_to_data_type(target_type):
def type_converter_decorator(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return target_type(result)
return wrapper
return type_converter_decorator
@convert_to_data_type(int)
def add_values(a, b):
return a + b
int_result = add_values(10, 20)
print("Result:", int_result, type(int_result))
@convert_to_data_type(str)
def concatenate_strings(str1, str2):
return str1 + str2
str_result = concatenate_strings("Python", " Decorator")
print("Result:", str_result, type(str_result))
输出结果:
Result: 30 <class 'int'>
Result: Python Decorator <class 'str'>
上面的代码示例展示了类型转换装饰器,它用来将函数的返回值转换为指定的数据类型。
名为convert_to_data_type()的装饰器将目标数据类型作为参数,并返回名为type_converter_decorator()的装饰器。在这个装饰器中,定义了一个wrapper()函数来调用原始函数,使用target_type()将其返回值转换为目标类型,然后返回转换后的结果。
在函数(如:add_values())上面应用@convert_to_data_type(int)的语法,利用这个装饰器将返回值转换为整数。类似地,对于concatenate_strings(),传递str将返回值格式化为字符串。该例子展示了装饰器如何在不改变函数核心逻辑的情况下,将函数输出无缝地修改为所需的格式。
- 应用场景
在需要自动调整函数以适应预期数据格式的应用程序中,这个返回值转换装饰器非常有用。
例如,你可以在天气API中使用它,该API在默认情况下以十进制格式返回温度,如23.456度。但是消费者前端应用程序希望显示整数值。当然不需要将API函数更改为返回整数,只需使用@convert_to_data_type(int)来修饰它。在返回到客户端应用程序之前,将无缝地将返回温度转换为整数23。
类似地,对于期望JSON的后置处理,可以使用@convert_to_data_type(JSON)装饰器转换返回值。核心逻辑保持不变,而表示格式根据实际需求进行调整。这避免了函数间格式处理代码的重复,以实现格式不匹配的应用程序层之间的无缝集成和可重用性。
参数验证装饰器
它会在执行前检查输入参数是否满足预定义的标准,从而增强函数的可靠性并防止意外行为。下面示例展示需要函数参数为正整数或非空字符串:
def check_condition_positive(value):
def argument_validator(func):
def validate_and_calculate(*args, **kwargs):
if value(*args, **kwargs):
return func(*args, **kwargs)
else:
raise ValueError("Invalid arguments passed to the function")
return validate_and_calculate
return argument_validator
@check_condition_positive(lambda x: x > 0)
def compute_cubed_result(number):
return number ** 3
print(compute_cubed_result(5)) # Output: 125
print(compute_cubed_result(-2)) # Raises ValueError: Invalid arguments passed to the function
输出结果:
125Traceback (most recent call last):
File "C:\\\\Program Files\\\\Sublime Text 3\\\\test.py", line 16, in <module>
print(compute_cubed_result(-2)) # Raises ValueError: Invalid arguments passed to the function
File "C:\\\\Program Files\\\\Sublime Text 3\\\\test.py", line 7, in validate_and_calculate
raise ValueError("Invalid arguments passed to the function")
ValueError: Invalid arguments passed to the function
这段代码展示了如何实现用于验证函数参数的装饰器。
check_condition_positive()
是装饰器工厂,它生成argument_validator()装饰器。当在compute_cubed_result()
函数之上使用@check_condition_positive()
时,该验证器将检查传递的参数的条件(在本例中,参数应大于0)是否为真。
如果满足条件,则执行修饰函数;否则引发ValueError
异常。这个简单示例说明了装饰器如何作为一种验证机制,在函数执行之前验证函数参数,确保符合指定的条件。
- 应用场景
参数验证装饰器在应用程序中非常有用,可以帮助执行业务规则检查、安全约束等。
举例,保险索赔处理系统有process_claim()
函数,它接受索赔id、审批人名称等详细信息。某些业务规则规定谁可以批准索赔。
您可以使用@check_condition_positive()
来修饰它,而不是使函数逻辑本身变得混乱,该方法验证审批者角色是否与索赔金额匹配。如果一个初级代理试图批准大额索赔(因此违反了规则),这个装饰器甚至在process_claim()
执行之前就会通过引发异常来捕获它。
类似地,可以在不触及单个具体功能的情况下,实施安全性和遵从性的输入数据验证约束。装饰器从外部确保违反规则的参数永远不会危及应用程序。我们可以跨多个函数重用公共验证模式,这不仅提高了安全性,也促进了关注点与规则验证的分离。
异常处理包装器
该装饰器是函数的安全网,优雅地处理异常,并在异常发生时提供默认响应。它可以防止应用程序因不可预见的情况而崩溃,确保平稳运行。
def handle_exceptions(default_response_msg):
def exception_handler_decorator(func):
def decorated_function(*args, **kwargs):
try:
# Call the original function
return func(*args, **kwargs)
except Exception as error:
# Handle the exception and provide the default response
print(f"Exception occurred: {error}")
return default_response_msg
return decorated_function
return exception_handler_decorator
# Example usage
@handle_exceptions(default_response_msg="An error occurred!")
def divide_numbers_safely(dividend, divisor):
return dividend / divisor
# Call the decorated function
result = divide_numbers_safely(7, 0) # This will raise a ZeroDivisionError
print("Result:", result)
输出结果:
Exception occurred: division by zero
Result: An error occurred!
这段代码展示了使用装饰器处理函数中的异常。
handle_exceptions()装饰器工厂接受默认响应,返回exception_handler_decorator()。当这个装饰器应用于函数时,它会尝试执行原始函数。如果出现异常,它打印错误细节,并返回指定的默认响应。
函数上面的@handle_exceptions()
语法包含了这种异常处理逻辑。例如,在divide_numbers_safely()中,除零会触发异常,装饰器会捕获该异常,从而防止崩溃并返回默认的 “An error occurred!” 响应。该装饰器安全地捕获函数异常,并提供无缝方法整合处理逻辑和防止崩溃。
- 应用场景
异常处理修饰器极大地简化了应用程序错误管理,并帮助对用户隐藏不可靠的行为。
举例,电子商务网站可能依赖于支付、库存和运输服务来完成订单。我们无需到处添加复杂的异常处理代码,仅仅在像place_order()核心订单处理函数上添加装饰器,以实现关注点分离,增加实现弹性。
通过@handle_exceptions装饰器,在订单完成期间会吸收任何第三方服务中断或间歇性问题。在异常情况下,它将记录错误以便调试,同时向客户提供优雅的“订单失败,请稍后再试”消息。从而避免了向最终用户暴露复杂的故障根源,如支付超时。
装饰师在不更改业务代码的情况下保护客户免受不可靠的服务问题的影响。它们在发生错误时提供友好的默认响应。这改善了客户体验,同时使开发人员能够在幕后看到错误信息。客户看到了更好的可靠性,而你获得了可操作的故障信息,所有这些都保持了原有业务逻辑不变。
总结
Python装饰器在应用程序开发中被广泛使用,它能够干净地插入常见的横切关注点。考虑到篇幅不要太长,我们下次再次分享几个典型装饰器应用场景,也期待你分享关于装饰器的应用场景。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)