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