最频繁使用的5个 Python 装饰器

装饰器是 Python 中非常强大的功能,可以使我们更加灵活地修改函数的行为和功能。

起初,每个 Python 开发者的目标都是让代码正常运行。慢慢地,我们开始担心可读性和可扩展性。这时候我们开始考虑装饰器。

装饰器是给函数添加额外行为的绝佳方式。而且有些小东西数据科学家经常需要注入到函数定义中。

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

以下是我在几乎每个数据项目中都会使用的五种最常见的工具。

技术交流

技术要学会分享、交流,不建议闭门造车。一个人可以走的很快、一堆人可以走的更远。

好的文章离不开粉丝的分享、推荐,资料干货、资料分享、数据、技术交流提升,均可加交流群获取,群友已超过2000人,添加时最好的备注方式为:来源+兴趣方向,方便找到志同道合的朋友。

方式①、添加微信号:dkl88191,备注:来自CSDN + python
方式②、微信搜索公众号: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, pd, 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, pd)
                    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]', pd='your_pd', recipient_email='[email protected]')
def my_function():
    # code that might fail

结论

装饰器是一种非常方便的方式,可以为我们的函数应用新行为。如果没有它们,将会有很多代码重复。

在本文中,我讨论了我最常用的装饰器。你可以根据自己的需求扩展这些内容。在我的所有项目中,我都使用这些装饰器的某个版本。尽管它们的行为略有不同,但这些是我经常使用装饰器实现共同目标。

猜你喜欢

转载自blog.csdn.net/qq_34160248/article/details/130666967
今日推荐