Декораторы в Python представляют функцию, которая в качестве параметра получает функцию и в качестве результата также возвращает функцию. Декораторы позволяют модифицировать выполняемую функцию, значения ее параметров и ее результат без изменения исходного кода этой функции.
Рассмотрим простейший пример:
# определение функции декоратора def select(input_func): def output_func(): # определяем функцию, которая будет выполняться вместо оригинальной print("*****************") # перед выводом оригинальной функции выводим всякую звездочки input_func() # вызов оригинальной функции print("*****************") # после вывода оригинальной функции выводим всякую звездочки return output_func # возвращаем новую функцию # определение оригинальной функции @select # применение декоратора select def hello(): print("Hello METANIT.COM") # вызов оригинальной функции hello()
Вначале определяется собственно функция декоратора, которая в данном случае называется select()
. В качестве параметра декоратор получает функцию (в данном случае параметр
input_func
), к которой этот декоратор будет применяться:
def select(input_func): def output_func(): # определяем функцию, которая будет выполняться вместо оригинальной print("*****************") # перед выводом оригинальной функции выводим всякую звездочки input_func() # вызов оригинальной функции print("*****************") # после вывода оригинальной функции выводим всякую звездочки return output_func # возвращаем новую функцию
Результатом декоратора в данном случае является локальная функция output_func
, в которой вызывается входная функция input_func. Для простоты здесь перед и после
вызыва input_func для красоты просто выводим набор символов "*".
Далее определяется стандартная функция, к которой применяется декоратор - в данном случае это функция hello
, которая просто выводит на консоль некоторую строку:
@select # применение декоратора select def hello(): print("Hello METANIT.COM")
Для применения декоратора перед определением функции указывается символ @
, после которого идет имя декоратора. То есть в данном случае к функции hello() применяется
декоратор select().
Далее вызываем обычную функцию:
hello()
Поскольку к этой функции применяется декоратор select, то в результате функциия hello передается в декоратор select()
в качестве параметра input_func
.
И поскольку декоратор возвращает новую функцию - output_func, то фактически в данном случае будет выполняться именно эта функция output_func()
В итоге мы получим следующий консольный вывод:
***************** Hello METANIT.COM *****************
Декоратор может перехватывать передаваемые в функцию аргументы:
# определение функции декоратора def check(input_func): def output_func(*args): # через *args получаем значения параметров оригинальной функции input_func(*args) # вызов оригинальной функции return output_func # возвращаем новую функцию # определение оригинальной функции @check def print_person(name, age): print(f"Name: {name} Age: {age}") # вызов оригинальной функции print_person("Tom", 38)
Здесь функция print_person()
принимает два параметра: name (имя) и age (возраст). К этой функции применяется декоратор check()
В декораторе check
возвращается локальная функция output_func()
, которая принимает некоторый набор значений в виде параметра *args
- это те
значения, которые передаются в оригинальную функцию, к которой применяется декоратор. То есть в данном случае *args
будет содержать значения параметров name и age.
def check(input_func): def output_func(*args): # через *args получаем значения параметров функции input_func
Здесь просто передаем эти значения в оригинальную функцию:
input_func(*args)
В итоге в данном получим следующий консольный вывод
Name: Tom Age: 38
Но что, если в функцию print_person будет передано какое-то недопустимое значение, например, отрицательный возраст? Одним из преимуществ декораторов как раз является то, что мы можем проверить и при необходимости модифицировать значения параметров. Например:
# определение функции декоратора def check(input_func): def output_func(*args): name = args[0] age = args[1] # получаем значение второго параметра if age < 0: age = 1 # если возраст отрицательный, изменяем его значение на 1 input_func(name, age) # передаем функции значения для параметров return output_func # определение оригинальной функции @check def print_person(name, age): print(f"Name: {name} Age: {age}") # вызов оригинальной функции print_person("Tom", 38) print_person("Bob", -5)
args фактически представляет набор значений, и, используя индексы, мы можем получить значения параметров по позиции и что-то с ними сделать. Так, здесь, если значение возраста меньше 0, то устанавливаем 1. Затем передаем эти значения в вызов функции. В итоге здесь получим следующий вывод:
Name: Tom Age: 38 Name: Bob Age: 1
Подобным образом можно получить результат функции и при необходимости изменить его:
# определение функции декоратора def check(input_func): def output_func(*args): result = input_func(*args) # передаем функции значения для параметров if result < 0: result = 0 # если результат функции меньше нуля, то возвращаем 0 return result return output_func # определение оригинальной функции @check def sum(a, b): return a + b # вызов оригинальной функции result1 = sum(10, 20) print(result1) # 30 result2 = sum(10, -20) print(result2) # 0
Здесь определена функция sum()
, которая возвращает сумму чисел. В декораторе check проверяем результат функции и для простоты, если он меньше нуля, то возвращаем 0.
Консольный вывод программы:
30 0