Декораторы Python — объяснение для начинающих

0
1508
views
python logo

Хочешь знать больше о Python?

Подпишись на наш канал о Python в Telegram!

×

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

В этой статье я расскажу о тонкостях использования paypal replica watches декораторов в Python и покажу примеры, где они могут быть полезны.

Функции в Python

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

Функция может принимать входные данные, использовать recommended site их для выполнения некоторого заранее определенного набора кода, а затем возвращать выходной результат.

В языке Python функция записывается следующим образом:

def add_one(num):
	return num + 1

Когда мы хотим вызвать функцию, мы можем написать ее имя, а в круглых скобках передать необходимые входные данные (аргументы):

final_value = add_one(1)
print(final_value) # 2

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

Разница заключается в том, где мы их обозначаем. Аргументы — это то, что мы передаем в функцию при ее вызове, а параметры — то, что объявлено в функции при ее создании.

Как передавать аргументы в функцию

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

Но можно также передавать в качестве аргумента и функцию:

def inner_function():
	print("inner_function is called")
    
def outer_function(func):
	print("outer_function is called")
 	func()
   
outer_function(inner_function)

# Результат:
# outer_function is called
# inner_function is called

В этом примере мы создаем две функции: inner_function и outer_function (внутренняя и внешняя функция соответственно).

Функция outer_function имеет параметр func. Это функция, которую outer_function вызывает после того, как сама была вызвана.

Схема работы внешней и внутренней функции

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

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

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

Как возвращать функции

Еще одно преимущество обращения с функциями как с объектами заключается в том, что мы можем определять их внутри других функций и возвращать:

def outer_function():
	print("outer_function is called")
    
	def inner_function():
    		print("inner_function is called")
      
	return inner_function

Обратите внимание: возвращая функцию inner_function, мы не вызывали ее. Мы вернули только ссылку на нее, чтобы затем сохранить и вызывать при необходимости:

returned_function = outer_function()
# outer_funciton is called

returned_function()
# inner_function is called

Вероятно, вам любопытно, причем тут декораторы. Сейчас мы это разберем!

Как создавать декораторы в Python

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

Декораторы используются для добавления дополнительной функциональности к существующим функциям.

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

def add_one_decorator(func):
    def add_one():
        value = func()
        return value + 1
        
    return add_one

Допустим, у нас есть функция, возвращающая число. Мы можем использовать этот декоратор для добавления единицы к любому значению, которое она выводит.

def example_function():
    return 1
    
final_value = add_one_decorator(example_function)
print(final_value()) # 2

В данном примере мы вызываем функцию add_one_decorator и передаем ссылку на example_function.

При вызове функции-декоратора add_one_decorator она создает новую функцию add_one, определенную внутри нее, и возвращает ссылку на эту новую функцию. Эту функцию мы храним в переменной final_value.

Таким образом, при выполнении функции final_value вызывается функция add_one.

Функция add_one, определенная внутри add_one_decorator, вызовет функцию example_function, сохранит ее значение и прибавит к нему единицу.

В итоге возвращается и выводится на консоль число 2.

Обратите внимание, что нам не пришлось изменять исходную функцию example_function, чтобы модифицировать возвращаемое ею значение. Именно это и делает декораторы такими полезными!

Хочу уточнить, что декораторы не являются специфическими для Python. Это концепция, которая может быть использована и в других языках программирования. Но в Python их можно легко использовать с помощью синтаксиса @.

Как использовать синтаксис @ в Python

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

В Python для большей эффективности можно использовать синтаксис @.

@add_one_decorator
def example_function():
    return 1

Если написать @add_one_decorator над нашей функцией, то это будет эквивалентно следующему:

example_function = add_one_decorator(example_function)

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

Как передавать аргументы с помощью декораторов

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

Допустим, у нас есть функция, которая требует два параметра и возвращает их сумму:

def add(a,b):
	return a + b
    
print(add(1,2)) # 3

Мы используем декоратор, который добавляет к результату единицу:

def add_one_decorator(func):
	def add_one():
    	value = func()
        return value + 1
        
    return add_one
    
@add_one_decorator
def add(a,b):
	return a + b
    
add(1,2)
# TypeError: add_one_decorator.<locals>.add_one() takes 0 positional arguments but 2 were given

При этом мы сталкиваемся с ошибкой: функция-обертка (add_one) не принимает никаких аргументов, а мы передали два.

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

def add_one_decorator(func):
	def add_one(*args, **kwargs):
    	value = func(*args, **kwargs)
        return value + 1
        
     return add_one
     
 @add_one_decorator
 def add(a,b):
 	return a+b
    
 print(add(1,2)) # 4

Мы используем *args и **kwargs для того, чтобы указать, что функция-обертка add_one может принимать любое количество позиционных (args) и именованных аргументов (kwargs).

args будет представлять собой список всех заданных позиционных ключевых слов, в данном случае [1,2].

kwargs будет представлять собой словарь. Ключами будут используемые именованные аргументы, а значениями — присвоенные им значения, в данном случае пустой словарь.

Запись func(*args, **kwargs) указывает на то, что мы хотим вызвать func с теми же позиционными и именованными аргументами, которые были получены.

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

Чем полезны декораторы в Python? Примеры реального кода

Теперь, когда мы рассмотрели, что такое декораторы в Python, давайте разберем несколько реальных примеров их использования.

Ведение лога

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

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

Вот пример того, как мы можем создать простой логгер для сохранения информации о нашем приложении во время его работы в файл main.log. Для этого используется встроенный пакет Python logging:

import logging

def function_logger(func):
    logging.basicConfig(level = logging.INFO, filename="main.log")
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        logging.info(f"{func.__name__} ran with positional arguments: {args} and keyword arguments: {kwargs}. Return value: {result}")
        return result
    
    return wrapper

@function_logger
def add_one(value):
    return value + 1

print(add_one(1))

При каждом выполнении функции add_one в файл main.log будет добавляться новый лог:

INFO:root:add_one ran with positional arguments: (1,) and keyword arguments: {}. Return value: 2

Кэширование

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

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

В Python это можно реализовать с помощью декоратора @lru_cache из модуля functools, поставляемого вместе с Python.

LRU расшифровывается как Least Recently Used. При каждом вызове функции будут сохраняться использованные аргументы и возвращаемое значение. Но как только количество таких записей достигнет максимального размера, последняя использованная запись будет удалена. Максимальный размер по умолчанию равен 128.

from functools import lru_cache

@lru_cache
def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

В данном примере функция fibonacci принимает аргумент n и, если он меньше 1, возвращает n. В противном случае возвращается сумма функций, вызванных с n-1 и n-2.

От редакции Techrocks: о рекурсивном вычислении последовательности Фибоначчи читайте в статье «Объяснение рекурсии на примерах».

Таким образом, если функция вызвана с n = 10, то она возвращает 55:

print(fibnoacci(10))
# 55

В этом случае, когда мы вызываем функцию fibonacci(10), она вызывает функцию fibonacci(9) и fibonacci(8), и так далее, пока не достигнет 1 или 0.

А если использовать эту функцию более одного раза…

fibonacci(50)
fibonacci(100)

…мы можем использовать кэш. Таким образом, когда мы вызываем fibonacci(50), вызов функции fibonacci может быть прекращен, как только дойдет до 10. А при вызове fibonacci(100) — как только достигнет 50. Это делает программу намного более эффективной.

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

Простое использование синтаксиса @ позволяет легко использовать дополнительные модули и пакеты.

Итоги

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

Перевод статьи «Python Decorators Explained For Beginners».

ОСТАВЬТЕ ОТВЕТ

Please enter your comment!
Please enter your name here