Декоратори Python, які можуть скоротити ваш код вдвічі

Андрій Губін

Блогер та Python-розробник Ayush Thakur зібрав у своєму блозі три корисні декоратори, які допоможуть розробникам скоротити код. А ми переклали для вас його блог. Тож далі слово автору.

Декоратори Python — це потужна фіча, яка дозволяє змінювати поведінку функції або класу без зміни його вихідного коду. По суті, це функції, які приймають іншу функцію як аргумент і повертають нову функцію, яка обгортає оригінальну. Таким чином, ви можете додати додаткову функціональність або логіку до оригінальної функції, не змінюючи її.

Наприклад, припустимо, у вас є функція, яка виводить повідомлення на консоль:

def hello():
print("Hello, world!")

Тепер припустимо, що ви хочете виміряти, скільки часу потрібно для виконання цієї функції. Ви можете написати іншу функцію, яка використовує модуль часу для обчислення часу виконання, а потім викликає вихідну функцію:

import time

def measure_time(func):
def wrapper():
start = time.time()
func()
end = time.time()
print(f"Execution time: {end - start} seconds")
return wrapper

Зверніть увагу, що функція measure_time повертає іншу функцію, яка називається wrapper, що є модифікованою версією оригінальної функції. Функція wrapper робить дві речі: записує час початку і закінчення виконання і викликає оригінальну функцію.

Тепер, щоб скористатися цією функцією, ви можете зробити щось на кшталт цього:

hello = measure_time(hello)
hello()

На виході вийде щось на кшталт цього:

Hello, world!
Execution time: 0.000123456789 seconds

Як бачите, ми успішно додали деяку додаткову функціональність до функції hello, не змінюючи її код. Однак є більш елегантний і лаконічний спосіб зробити це за допомогою декораторів.

Декоратори — це просто синтаксичний цукор, який дозволяє застосовувати функцію до іншої функції за допомогою символу @.

Наприклад, ми можемо переписати попередній код таким чином:

@measure_time
def hello():
print("Hello, world!")

hello()

Це дасть той самий результат, що і раніше, але з набагато меншою кількістю коду. Рядок @measure_time еквівалентний команді hello = measure_time(hello), але виглядає набагато чистіше і читабельніше.

Навіщо використовувати декоратори Python?

Декоратори Python корисні з багатьох причин, таких як:

  • Вони дозволяють повторно використовувати код і уникати повторень. Наприклад, якщо у вас багато функцій, які потребують вимірювання часу виконання, ви можете просто застосувати до них один і той самий декоратор замість того, щоб писати один і той самий код знову і знову.
  • Вони дозволяють розділити завдання і дотримуватися принципу єдиної відповідальності. Наприклад, якщо у вас є функція, яка виконує складні обчислення, ви можете використовувати декоратор для ведення журналу, обробки помилок, кешування або перевірки вхідних і вихідних даних, не заважаючи основній логіці роботи функції.
  • Вони дозволяють розширити функціональність існуючих функцій або класів, не змінюючи їхній вихідний код. Наприклад, якщо ви використовуєте сторонню бібліотеку, яка надає деякі корисні функції або класи, але хочете додати до них додаткові можливості або поведінку, ви можете використовувати декоратори, щоб обернути їх і налаштувати відповідно до ваших потреб.

Деякі приклади декораторів Python

У Python є багато вбудованих декораторів, таких як @staticmethod, @classmethod, @property, @functools.lru_cache, @functools.singledispatch тощо. Ви також можете створювати власні кастомні декоратори для різних цілей.

Ось кілька прикладів декораторів Python, які можуть скоротити ваш код вдвічі:

Декоратор @timer

Цей декоратор схожий на декоратор @measure_time, який ми розглядали раніше, але його можна застосувати до будь-якої функції, яка приймає будь-яку кількість аргументів і повертає будь-яке значення. Він також використовує декоратор functools.wraps для збереження імені та документації оригінальної функції.

Ось код:

import time
from functools import wraps

def timer(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"Execution time of {func.__name__}: {end - start} seconds")
return result
return wrapper

Тепер ви можете використовувати цей декоратор для вимірювання часу виконання будь-якої функції, наприклад:

@timer
def factorial(n):
"""Returns the factorial of n"""
if n == 0 or n == 1:
return 1
else:
return n * factorial(n - 1)

@timer
def fibonacci(n):
"""Returns the nth Fibonacci number"""
if n == 0 or n == 1:
return n
else:
return fibonacci(n - 1) + fibonacci(n - 2)

print(factorial(10))
print(fibonacci(10))

На виході вийде щось на кшталт цього:

Execution time of factorial: 1.1920928955078125e-06 seconds
3628800
Execution time of fibonacci: 0.000123456789 seconds
55

Декоратор @debug

Цей декоратор корисний для дебагінгу, оскільки він виводить назву, аргументи і значення, що повертається, функції, яку він обгортає. Він також використовує декоратор functools.wraps для збереження імені та документації оригінальної функції. Ось код:

from functools import wraps

def debug(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__} with args: {args} and kwargs: {kwargs}")
result = func(*args, **kwargs)
print(f"{func.__name__} returned: {result}")
return result
return wrapper

Тепер ви можете використовувати цей декоратор для налагодження будь-якої функції, наприклад:

@debug
def add(x, y):
"""Returns the sum of x and y"""
return x + y

@debug
def greet(name, message="Hello"):
"""Returns a greeting message with the name"""
return f"{message}, {name}!"

print(add(2, 3))
print(greet("Alice"))
print(greet("Bob", message="Hi"))

На виході отримаємо щось на кшталт цього:

Calling add with args: (2, 3) and kwargs: {}
add returned: 5
5
Calling greet with args: ('Alice',) and kwargs: {}
greet returned: Hello, Alice!
Hello, Alice!
Calling greet with args: ('Bob',) and kwargs: {'message': 'Hi'}
greet returned: Hi, Bob!
Hi, Bob!

Декоратор @memoize

Цей декоратор корисний для оптимізації продуктивності рекурсивних або дорогих функцій, оскільки він кешує результати попередніх викликів і повертає їх у разі повторної передачі тих самих аргументів. Він також використовує декоратор functools.wraps для збереження імені та документації вихідної функції. Нижче наведено код:

from functools import wraps

def memoize(func):
cache = {}
@wraps(func)
def wrapper(*args):
if args in cache:
return cache[args]
else:
result = func(*args)
cache[args] = result
return result
return wrapper

Тепер ви можете використовувати цей декоратор для запам’ятовування будь-якої функції, наприклад:

@memoize
def factorial(n):
"""Returns the factorial of n"""
if n == 0 or n == 1:
return 1
else:
return n * factorial(n - 1)
@memoize
def fibonacci(n):
"""Returns the nth Fibonacci number"""
if n == 0 or n == 1:
return n
else:
return fibonacci(n - 1) + fibonacci(n - 2)
print(factorial(10))
print(fibonacci(10))

Це дасть той самий результат, що й раніше, але зі значно швидшим часом виконання, оскільки результати кешуються і використовуються повторно.

Висновок

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

Останні статті

Юлія Штукатурова стала новою керівницею європейського регіону в GlobalLogic

Компанія GlobalLogic заявила, що європейський регіон відтепер очолюватиме Юлія Штукатурова, повідомили в DOU. У GlobalLogic Юлія…

04.06.2025

Інсайдери OpenAI розповіли, що чекати від GPT-5, яка з’явиться в липні

Флагманська LLM-модель OpenAI GPT-5 з'явиться вже в липні, як стверджують інсайдери. Модель матиме підтримку відео,…

04.06.2025

Безкоштовні користувачі ChatGPT отримали доступ до розширеної функції пам’яті

Функція пам'яті ChatGPT, яка дозволяє звертатися до історії минулих розмов з чат-ботом, тепер стала доступною…

04.06.2025

Розробники Go остаточно відмовились від спроб покращити синтаксис обробки помилок

Після шести років обговорень та сотень пропозицій від ком'юніті команда розробників Go офіційно оголосила, що…

04.06.2025

Оновлення Telegram: з’явились повідомлення адмінам каналів і скорочення голосових повідомлень

Команда Telegram випустила чергове велике оновлення. Адмінам каналів тепер можна надсилати особисті повідомлення, спростилося перемикання…

04.06.2025

У Росії націоналізували одну з найбільших геймдев-компаній. Звинуватили в підтримці ЗСУ

Таганський суд Москви ухвалив рішення про передачу у власність держави 100% уставного капіталу IT-компанії «Леста…

04.06.2025