
Зображення до статті
Андрій Губін
Журналіст
Декоратори в Python — це потужна функціональність, яка дає змогу модифікувати поведінку існуючої функції або класу, не змінюючи їхній вихідний код. По суті, це функції, які отримують іншу функцію як параметр і повертають нову функцію, яка обертає оригінальну. Таким чином, ви можете додати додаткові можливості або логіку до вихідної функції, не вносячи змін до її коду.
Припустимо, у вас є функція, яка виводить повідомлення на консоль:
def hello():
print(“Hello, world!”)
Тепер уявіть, що ви хочете виміряти час виконання цієї функції. Ви можете створити окрему функцію, яка використовує модуль `time` для обчислення тривалості виконання, а потім викликає вихідну функцію:
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 — це потужний і елегантний спосіб змінювати поведінку функцій або класів, не редагуючи їхній вихідний код. Вони можуть допомогти вам зменшити обсяг коду вдвічі, покращити його читабельність, сприяти повторному використанню коду, відокремлювати відповідальності та розширювати функціональність наявного коду.
Головна > Добірки > Декоратори Python, які можуть скоротити ваш код вдвічі
