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

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

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

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

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

Функции-обёртки Python

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

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

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

Примеры

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

1 — 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, нам нужно определить вызываемую функцию timer, которая принимает вызываемый параметр func, указывающий, что это функция декоратора. Внутри функции timer мы определяем другую функцию с именем wrapper, которая принимает аргументы, обычно передаваемые функции, которую мы хотим декорировать.

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

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

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

@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() 

Результат:

Запуск функции обучения модели…

Обучение моделей завершено!

Время выполнения: 5,006425619125366 секунд

2 — Debugger

Можно создать дополнительную полезную функцию-оболочку для облегчения отладки путем вывода входных и выходных данных каждой функции. Этот подход позволяет нам получить представление о потоке выполнения различных функций, не загромождая наши приложения многочисленными операторами 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

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

@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
3 — 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

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

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

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

Другой способ сделать это — создать бесчисленное количество строк 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

Чтобы вызвать проверенный ввод, нам нужно определить функции валидации. Например, можно использовать две функции проверки. Первая функция (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
5 — Retry

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

Чтобы реализовать это, мы можем определить другую функцию-оболочку для нашего декоратора, как в нашем предыдущем примере. Однако на этот раз вместо того, чтобы предоставлять функции проверки в качестве входных переменных, мы можем передавать определенные параметры, такие как max_attemps и 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

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

@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:

  • Timer Wrapper
  • Debugger Wrapper
  • Exception Handler Wrapper
  • Input Validator Wrapper
  • Function Retry Wrapper

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

+1
2
+1
3
+1
0
+1
1
+1
0

Ответить

Ваш адрес email не будет опубликован. Обязательные поля помечены *