5个python常用的装饰器!

大家好呀,我是阿潘。

首先,每个开发人员的目标都是让事情正常进行。慢慢地,我们担心可读性和可扩展性。这是我们第一次开始考虑装饰器的时候。

装饰器是为函数提供额外行为的绝佳方式。 

使用装饰器,你会惊讶地发现可以减少代码重复并提高可读性。

以下是我在几乎每个数据密集型项目中使用的五个最常用的方法。

1.重试装饰器

在数据科学项目和软件开发项目中,有很多我们依赖外部系统的情况。事情并不总是在我们的控制之中。

当意外事件发生时,我们可能希望我们的代码等待一段时间,让外部系统自行纠正并重新运行。

我更喜欢在 python 装饰器中实现这个重试逻辑,这样我就可以注释任何函数来应用重试行为。

这是重试装饰器的代码。

import time
from functools import wraps
def retry(max_tries=3, delay_seconds=1):
    def decorator_retry(func):
        @wraps(func)
        def wrapper_retry(*args, **kwargs):
            tries = 0
            while tries < max_tries:
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    tries += 1
                    if tries == max_tries:
                        raise e
                    time.sleep(delay_seconds)
        return wrapper_retry
    return decorator_retry
@retry(max_tries=5, delay_seconds=2)
def call_dummy_api():
    response = requests.get("https://jsonplaceholder.typicode.com/todos/1")
    return response

在上面的代码中,我们尝试获取 API 响应。如果失败,我们将重试相同的任务 5 次。在每次重试之间,我们等待 2 秒。

2.缓存函数结果

我们代码库的某些部分很少改变它们的行为。然而,它可能会占用我们很大一部分计算能力。在这种情况下,我们可以使用装饰器来缓存函数调用。

如果输入相同,该函数将只运行一次。在随后的每次运行中,结果将从缓存中提取。因此,我们不必一直执行昂贵的计算。

def memoize(func):
    cache = {}
    def wrapper(*args):
        if args in cache:
            return cache[args]
        else:
            result = func(*args)
            cache[args] = result
            return result
    return wrapper

装饰器使用字典,存储函数参数,并返回值。当我们执行此功能时,装饰器将检查字典中的先前结果。只有在之前没有存储值时才会调用实际函数。

下面是一个计算斐波那契数列的函数。由于这是一个循环函数,所以调用的同一个函数会执行多次。但是通过缓存,我们可以加快这个过程。

@memoize
def fibonacci(n):
    if n <= 1:
        return n
    else:
        return fibonacci(n-1) + fibonacci(n-2)

以下是使用和不使用缓存时此函数的执行时间。请注意,缓存版本只需要几分之一毫秒即可运行,而非缓存版本几乎需要一分钟。

Function slow_fibonacci took 53.05560088157654 seconds to run.
Function fast_fibonacci took 7.772445678710938e-05 seconds to run.

使用字典来保存以前的执行数据是一种直接的方法。但是,有一种更复杂的方法来存储缓存数据。您可以使用内存数据库,例如 Redis。

3.计时功能

这一点并不奇怪。在处理数据密集型函数时,我们渴望了解运行需要多长时间。

通常的做法是收集两个时间戳,一个在函数的开头,另一个在函数的结尾。然后我们可以计算持续时间并将其与返回值一起打印。

但是一次又一次地为多个函数这样做是一件麻烦事。

相反,我们可以让装饰者来做。我们可以注释任何需要打印持续时间的函数。

这是一个 Python 装饰器示例,它在调用函数时打印函数的运行时间:

import time


def timing_decorator(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"Function {func.__name__} took {end_time - start_time} seconds to run.")
        return result
    return wrapper

您可以使用此装饰器来计时函数的执行:

@timing_decorator
def my_function():
    # some code here
    time.sleep(1)  # simulate some time-consuming operation
    return

调用该函数将打印运行所需的时间。

my_function()


>>> Function my_function took 1.0019128322601318 seconds to run.

4.记录函数调用这个在很大程度上是前一个装饰器的扩展。但它有一些特殊用途。

如果您遵循软件设计原则,您会喜欢单一职责原则。这实质上意味着每个功能将承担其唯一的责任。

当您以这种方式设计代码时,您还希望记录函数的执行信息。这就是日志装饰器派上用场的地方。

下面的例子说明了这一点。

import logging
import functools


logging.basicConfig(level=logging.INFO)


def log_execution(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        logging.info(f"Executing {func.__name__}")
        result = func(*args, **kwargs)
        logging.info(f"Finished executing {func.__name__}")
        return result
    return wrapper


@log_execution
def extract_data(source):
    # extract data from source
    data = ...


    return data


@log_execution
def transform_data(data):
    # transform data
    transformed_data = ...


    return transformed_data


@log_execution
def load_data(data, target):
    # load data into target
    ...


def main():
    # extract data
    data = extract_data(source)


    # transform data
    transformed_data = transform_data(data)


    # load data
    load_data(transformed_data, target)

上面的代码是 ETL 管道的简化版本。我们有三个独立的函数来处理每个提取、转换和加载。我们已经使用我们的 log_execution 装饰器包装了它们中的每一个。

现在,无论何时执行代码,您都会看到类似这样的输出:

INFO:root:Executing extract_data
INFO:root:Finished executing extract_data
INFO:root:Executing transform_data
INFO:root:Finished executing transform_data
INFO:root:Executing load_data
INFO:root:Finished executing load_data

我们还可以在这个装饰器中打印执行时间。但我希望将它们都放在不同的装饰器中。这样,我就可以选择将哪一个(或两者)用于一个函数。

以下是如何在单个函数上使用多个装饰器。

@log_execution
@timing_decorator
def my_function(x, y):
    time.sleep(1)
    return x + y

5.通知装饰器

最后,生产系统中一个非常有用的装饰器是通知装饰器。

再一次,即使重试几次,即使是经过良好测试的代码库也会失败。当发生这种情况时,我们需要通知相关人员以迅速采取行动。

如果您曾经构建过数据管道并希望它能永远正常工作,那么这并不是什么新鲜事。

每当内部函数执行失败时,以下装饰器都会发送一封电子邮件。在您的案例中,它不一定是电子邮件通知。您可以将其配置为发送 Teams/slack 通知。

import smtplib
import traceback
from email.mime.text import MIMEText


def email_on_failure(sender_email, password, recipient_email):
    def decorator(func):
        def wrapper(*args, **kwargs):
            try:
                return func(*args, **kwargs)
            except Exception as e:
                # format the error message and traceback
                err_msg = f"Error: {str(e)}\n\nTraceback:\n{traceback.format_exc()}"
                
                # create the email message
                message = MIMEText(err_msg)
                message['Subject'] = f"{func.__name__} failed"
                message['From'] = sender_email
                message['To'] = recipient_email
                
                # send the email
                with smtplib.SMTP_SSL('smtp.gmail.com', 465) as smtp:
                    smtp.login(sender_email, password)
                    smtp.sendmail(sender_email, recipient_email, message.as_string())
                    
                # re-raise the exception
                raise
                
        return wrapper
    
    return decorator


@email_on_failure(sender_email='[email protected]', password='your_password', recipient_email='[email protected]')
def my_function():
    # code that might fail

结论装饰器是将新行为应用于我们的函数的一种非常方便的方法。没有它们,就会有很多代码重复。

在这篇文章中,我讨论了我最常用的装饰器。您可以根据您的特定需求扩展这些。例如,您可以使用 Redis 服务器来存储缓存响应而不是字典。这将使您能够更好地控制数据,例如持久性。或者您可以调整代码以逐步增加重试装饰器中的等待时间。

在我所有的项目中,我都使用了这些装饰器的某些版本。尽管它们的行为略有不同,但这些是我经常使用装饰器的共同目标。

我希望这篇文章对你有所帮助。

本篇文章翻译自:https://towardsdatascience.com/python-decorators-for-data-science-6913f717669a

猜你喜欢

转载自blog.csdn.net/flyfor2013/article/details/129787279
今日推荐