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

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

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

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

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

Обертки Python

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

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

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

Примеры

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

1 — Таймер

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

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

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

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

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

@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 — Отладчик

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

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__, чтобы получить имя вызываемой функции, а затем параметры 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

3 — Обработчик исключений

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 — Входной валидатор

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

Другой способ сделать это — создать бесчисленное количество 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 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!")  # Output: Hello! 1.0

5 — Повторить попытку

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

  • Обертка таймера
  • Оболочка отладчика
  • Оболочка обработчика исключений
  • Оболочка валидатора ввода
  • Обертка повторной попытки функции

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

Понравилась эта история?

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

Хотите читать более трех бесплатных историй в месяц? — Станьте участником Medium за 5$/месяц. Вы можете поддержать меня, используя мою реферальную ссылку при регистрации. Я получу комиссию без дополнительных затрат для вас.

Если эта статья предоставила вам решение, которое вы искали, вы можете поддержать меня в моем личном аккаунте. Ваша поддержка всегда приветствуется ❤

Дополнительные материалы на PlainEnglish.io.

Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Подпишитесь на нас в Twitter, LinkedIn, YouTube и Discord .