Декораторы Python, которые могут сократить ваш код вдвое

Декораторы Python позволяют повысить эффективность и читаемость кода в Python. В статье представлены полезные примеры, для решения различных задач.

Python — самый любимый язык программирования благодаря его простому синтаксису и мощным возможностям в различных областях, таких как машинное обучение и веб-разработка.

Декораторы редко привлекают внимание, если только они не были абсолютно необходимы, например, при использовании декоратора @staticmethod для обозначения статического метода внутри класса.

Однако мое восприятие изменилось во время одного пул реквеста, когда мой коллега представил декоратор таймера для одной из функций. Это пробудило мою любопытство о множестве других возможностей, которые могут предложить декораторы, повышая чистоту и читаемость кода.

Поэтому в этой небольшой статье мы рассмотрим концепцию декораторов Python и представим пять примеров, которые могут улучшить наш процесс разработки на Python.

Декораторы Python

Декораторы Python — это функции, добавляемые к другой функции, которые могут добавлять дополнительную функциональность или изменять ее поведение, не изменяя непосредственно исходный код функции. Они обычно реализуются в виде декораторов — особых функций, которые принимают другую функцию на вход и вносят в нее некоторые изменения.

Функции обертки могут быть полезны в различных сценариях:

  • Расширение функциональности: Мы можем добавить функции, такие как логирование, измерение производительности или кэширование, обернув наши функции с помощью декоратора.
  • Повторное использование кода: Мы можем применить функцию обертки или даже класс к нескольким сущностям, избежав дублирования кода и обеспечивая однородное поведение для различных компонентов.
  • Изменение поведения: Мы можем перехватывать входные аргументы, например, проверять входную переменную без необходимости использования множества утверждений.

Примеры

Позвольте мне показать вам несколько примеров, которые делают обертки необходимыми в нашей повседневной работе:

timer

Эта функция-обертка измеряет время выполнения функции и выводит затраченное время. Это может быть полезно для профилирования и оптимизации кода.

import time

def timer(func):
    def wrapper(*args, **kwargs):
        # start the timer
        start_time = time.time()
        # call the decorated function
        result = func(*args, **kwargs)
        # remeasure the time
        end_time = time.time()
        # compute the elapsed time and print it
        execution_time = end_time - start_time
        print(f"Execution time: {execution_time} seconds")
        # return the result of the decorated function execution
        return result
    # return reference to the wrapper function
    return wrapper
Python

Чтобы создать декоратор в Python, мы должны определить функцию с именем timer, которая принимает параметр с именем func для указания того, что это функция-декоратор. Внутри функции timer мы определяем другую функцию с именем wrapper, которая принимает аргументы, обычно передаваемые в функцию, которую мы хотим задекорировать.

Внутри функции wrapper мы вызываем желаемую функцию, используя предоставленные аргументы. Мы можем сделать это с помощью строки: result = func(*args, **kwargs).

Наконец, функция wrapper возвращает результат выполнения задекорированной функции. Функция-декоратор должна возвращать ссылку на функцию обертки, которую мы только что создали.

Чтобы использовать декоратор, вы можете применить его к желаемой функции с помощью символа @.

@timer
def train_model():
    print("Starting the model training function...")
    # simulate a function execution by pausing the program for 5 seconds
    time.sleep(5) 
    print("Model training completed!")

train_model() 
Python

Вывод:

Starting the model training function…

Model Training completed!

Execution time: 5.006425619125366 seconds
Python

debug

Еще одна полезная функция-декоратор может быть создана для упрощения отладки. Упрощения путем вывода входных и выходных данных каждой функции. Такой подход позволяет нам получить представление о потоке выполнения различных функций. Без загромождения нашего приложения множеством инструкций print.

def debug(func):
    def wrapper(*args, **kwargs):
        # print the fucntion name and arguments
        print(f"Calling {func.__name__} with args: {args} kwargs: {kwargs}")
        # call the function
        result = func(*args, **kwargs)
        # print the results
        print(f"{func.__name__} returned: {result}")
        return result
    return wrapper
Python

Мы можем использовать параметры __name__, чтобы получить имя вызываемой функции, а затем параметры args и kwargs, чтобы выводить переданные функции аргументы.

@debug
def add_numbers(x, y):
    return x + y
add_numbers(7, y=5,)  # Output: Calling add_numbers with args: (7) kwargs: {'y': 5} \n add_numbers returned: 12
Python

exception_handler

Декоратора exception_handler перехватывает любые исключения, возникающие внутри функции divide, и обрабатывает их соответствующим образом.

Мы можем настраивать декоратор исключений внутри функции обертки в соответствии с вашими требованиями, например, регистрировать исключение или выполнять дополнительные шаги обработки ошибок.

def exception_handler(func):
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception as e:
            # Handle the exception
            print(f"An exception occurred: {str(e)}")
            # Optionally, perform additional error handling or logging
            # Reraise the exception if needed
    return wrapper
Python

Это становится очень полезным для упорядочения кода и установления единой процедуры обработки исключений.

@exception_handler
def divide(x, y):
    result = x / y
    return result
divide(10, 0)  # Output: An exception occurred: division by zero
Python

validate_input

Эта функция-декоратор проверяет входные аргументы функции на соответствие заданным условиям или типам данных. Она может использоваться для обеспечения правильности и последовательности входных данных.

Другой подход — создание множества утверждений assert внутри функции, которую мы хотим использовать для проверки входных данных.

Для добавления проверок в декоратор нам нужно обернуть функцию-декоратор в другую функцию. Которая принимает одну или несколько функций проверки в качестве аргументов. Эти функции проверки отвечают за проверку, соответствуют ли входные значения определенным критериям или условиям.

Функция validate_input теперь действует как декоратор. Внутри функции обертки входные аргументы и аргументы ключевых слов проверяются с использованием предоставленных функций проверки. Если какой-либо аргумент не проходит проверку, возникает 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"Invalid argument: {val}")
            for key, val in kwargs.items():
                if key in validations[len(args):]:
                    if not validations[len(args):][key](val):
                        raise ValueError(f"Invalid argument: {key}={val}")
            return func(*args, **kwargs)
        return wrapper
    return decorator
Python

Для вызова кода проверки ввода необходимо определить функции проверки. Например, можно использовать две функции проверки. Первая функция (lambda x: x > 0) проверяет, является ли аргумент x больше нуля, и вторая функция (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!")  # Output: Hello! 1.0
Python

retry

Эта функция-декоратор повторяет выполнение функции указанное количество раз с задержкой между попытками. Это может быть полезно при работе с сетью или вызовами 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"Attempt {attempts} failed: {e}")
                    time.sleep(delay)
            print(f"Function failed after {max_attempts} attempts")
        return wrapper
    return decorator
Python

Для вызова функции мы можем указать максимальное количество попыток и продолжительность времени в секундах между каждым вызовом функции.

@retry(max_attempts=3, delay=2)
def fetch_data(url):
    print("Fetching the data..")
    # raise timeout error to simulate a server not responding..
    raise TimeoutError("Server is not responding.")
fetch_data("https://example.com/data")  # Retries 3 times with a 2-second delay between attempts
Python

Заключение

Декораторы Python — это мощные инструменты, которые могут повысить ваш опыт программирования на Python. Используя обертки, вы можете упростить сложные задачи, улучшить читаемость кода и повысить производительность.

В этой статье мы рассмотрели пять примеров оберток Python:

  • Таймер
  • Отладчик
  • Обработчик исключений
  • Валидатор входных данных
  • Повторение

Включение декораторов в ваши проекты поможет вам писать более чистый и эффективный код на Python. А так же поднять ваш уровень программирования на новый уровень.


Опубликовано

в

от

Комментарии

Добавить комментарий