Python是我最喜欢的编程语言,因为它的语法简单,可以在机器学习和Web开发等各个领域中应用。
虽然我已经编程五年了,但装饰器在我的视野中很少出现,除非绝对必要,比如使用@staticmethod
装饰器来表示类中的静态方法。
然而,在最近的合并请求审查中,当我的同事向我的一个函数引入了一个计时器装饰器时,我的看法发生了改变。这激发了我对装饰器可以提供的其他功能的好奇心,提高了代码的整洁性和可读性。
因此,在这篇简短的文章中,我们将探讨Python包装器的概念,并介绍五个可以改进我们的Python开发过程的示例。
Python包装器是添加到另一个函数中的函数,它可以在不直接更改源代码的情况下添加附加功能或修改其行为。它们通常被实现为装饰器,装饰器是一种特殊的函数,它以另一个函数作为输入,并对其功能进行一些更改。
包装器函数在各种场景中非常有用:
-
功能扩展:我们可以通过使用装饰器将日志记录、性能测量或缓存等功能添加到我们的函数中。
-
代码重用:我们可以将包装器函数或甚至类应用于多个实体,以避免代码重复,并确保在不同组件之间保持一致的行为。
-
行为修改:我们可以拦截输入参数,例如验证输入变量,而无需使用许多
assert
语句。
让我给您展示一些在我们日常工作中必不可少的示例:
这个包装器函数测量函数的执行时间并打印出经过的时间。它对于性能分析和代码优化非常有用。
import time
def timer(func):
def wrapper(*args, **kwargs):
# 开始计时
start_time = time.time()
# 调用被装饰的函数
result = func(*args, **kwargs)
# 重新计时
end_time = time.time()
# 计算经过的时间并打印出来
execution_time = end_time - start_time
print(f"执行时间:{execution_time}秒")
# 返回被装饰的函数的执行结果
return result
# 返回对包装函数的引用
return wrapper
要在Python中创建装饰器,我们需要定义一个名为timer
的函数,该函数接受一个名为func
的参数,以指示它是一个装饰器函数。在timer
函数内部,我们定义另一个名为wrapper
的函数,该函数接受通常传递给我们要装饰的函数的参数。
在wrapper
函数内部,我们使用提供的参数调用所需的函数。我们可以使用以下代码行来实现这一点:result = func(*args, **kwargs)
。
最后,wrapper
函数返回被装饰函数的执行结果。装饰器函数应该返回对我们刚刚创建的wrapper函数的引用。
要使用装饰器,您可以使用@
符号将其应用于所需的函数。
@timer
def train_model():
print("开始模型训练函数...")
# 通过暂停程序5秒钟来模拟函数执行
time.sleep(5)
print("模型训练完成!")
train_model()
输出:
开始模型训练函数...
模型训练完成!
执行时间:5.006425619125366秒
另一个有用的包装器函数可以用于简化调试,通过打印每个函数的输入和输出来帮助我们了解各个函数的执行流程,而不会在应用程序中添加多个打印语句。
def debug(func):
def wrapper(*args, **kwargs):
# 打印函数名和参数
print(f"调用 {func.__name__},参数:{args},关键字参数:{kwargs}")
# 调用函数
result = func(*args, **kwargs)
# 打印结果
print(f"{func.__name__} 返回:{result}")
return result
return wrapper
我们可以使用__name__
参数来获取被调用的函数的名称,然后使用args
、kwargs
参数来打印传递给函数的参数。
@debug
def add_numbers(x, y):
return x + y
add_numbers(7, y=5,) # 输出:调用 add_numbers,参数:(7),关键字参数:{'y': 5} \n add_numbers 返回:12
exception_handler
装饰器将捕获divide
函数中引发的任何异常并进行相应的处理。
我们可以根据需要自定义包装器函数中的异常处理,例如记录异常或执行其他错误处理步骤。
def exception_handler(func):
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception as e:
# 处理异常
print(f"发生异常:{str(e)}")
# 可选地,执行其他错误处理或记录
# 如果需要,重新引发异常
return wrapper
这对于简化我们的代码并建立统一的异常处理和错误日志记录流程非常有用。
@exception_handler
def divide(x, y):
result = x / y
return result
divide(10, 0) # 输出:发生异常:division by zero
这个包装器函数根据指定的条件或数据类型验证函数的输入参数。它可以用于确保输入数据的正确性和一致性。
另一种方法是在我们要验证输入数据的函数内部创建无数个
assert
语句。
要向装饰器添加验证,我们需要使用另一个函数将装饰器函数包装起来,该函数接受一个或多个验证函数作为参数。这些验证函数负责检查输入值是否满足某些条件或标准。
validate_input
函数本身现在充当装饰器。在wrapper
函数内部,将检查输入和关键字参数是否符合提供的验证函数。如果任何参数未通过验证,它将引发一个带有指示无效参数的消息的ValueError
。
def validate_input(*validations):
def decorator(func):
def wrapper(*args, **kwargs):
for i, val in enumerate(args):
if i < len(validations):
if not validations[i](val):
raise ValueError(f"无效参数:{val}")
for key, val in kwargs.items():
if key in validations[len(args):]:
if not validations[len(args):][key](val):
raise ValueError(f"无效参数:{key}={val}")
return func(*args, **kwargs)
return wrapper
return decorator
要调用经过验证的输入,我们需要定义验证函数。例如,可以使用两个验证函数。第一个函数(lambda x: x > 0
)检查参数x
是否大于0,第二个函数(lambda y: isinstance(y, str)
)检查参数y
是否为字符串类型。
确保验证函数的顺序与它们要验证的参数的顺序相对应非常重要。
@validate_input(lambda x: x > 0, lambda y: isinstance(y, str))
def divide_and_print(x, message):
print(message)
return 1 / x
divide_and_print(5, "Hello!") # 输出:Hello! 1.0
这个包装器函数会在指定的次数内重试执行函数,并在重试之间延迟一段时间。当处理由于临时问题而偶尔失败的网络或API调用时,它非常有用。
要实现这一点,我们可以向装饰器添加另一个包装器函数,类似于我们之前的示例。但是这次,我们不再提供验证函数作为输入变量,而是可以传递特定的参数,如max_attempts
和delay
。
当调用被装饰的函数时,将调用wrapper
函数。它会跟踪尝试的次数(从0开始)并进入一个while循环。循环尝试执行被装饰的函数,并在成功时立即返回结果。但是,如果发生异常,它会增加尝试计数器并打印一个错误消息,指示尝试编号和发生的具体异常。然后,它使用time.sleep
等待指定的延迟时间,然后再次尝试执行函数。
import time
def retry(max_attempts, delay=1):
def decorator(func):
def wrapper(*args, **kwargs):
attempts = 0
while attempts < max_attempts:
try:
return func(*args, **kwargs)
except Exception as e:
attempts += 1
print(f"第{attempts}次尝试失败:{e}")
time.sleep(delay)
print(f"在{max_attempts}次尝试后函数失败")
return wrapper
return decorator
为了调用该函数,我们可以指定最大尝试次数和每次调用函数之间的时间间隔(以秒为单位)。
@retry(max_attempts=3, delay=2)
def fetch_data(url):
print("正在获取数据..")
# 模拟服务器无响应的超时错误..
raise TimeoutError("服务器无响应。")
fetch_data("https://example.com/data") # 重试3次,每次间隔2秒
Python包装器是强大的工具,可以提升您的Python编程体验。通过使用包装器,您可以简化复杂的任务,提高代码可读性,并提高生产力。
在本文中,我们探讨了五个Python包装器的示例:
-
计时器包装器
-
调试器包装器
-
异常处理器包装器
-
输入验证器包装器
-
函数重试包装器
将这些包装器应用到您的项目中,将有助于编写更清晰、更高效的Python代码,并提升您的编程技能。