July 18, 2018

Декораторы

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

from datetime import datetime                          
                                         
                                         
def date_logger(): # Выводит текущую дату и время                
  print(datetime.now().strftime("%Y-%m-%d %H:%M:%S"))             
                                         
                                         
def connection_logger(connected): # Выводит статус соединения          
  if connected:                                
    print("Connected")                            
  else:                                    
    print("Not connected")

Мы где-то используем их в коде. Всё хорошо, но тут заказчик говорит, что перед каждым логом надо выводить "Log:". Зачем? А кто знает, надо и надо.

Конечно можно в начале каждой функции выводить это слово. Однако функций может быть много, а завтра заказчик захочет выводить другое слово. Хочется чего-то более универсального. Тут кто-то предложит глобальную переменную, но это не всегда лучший способ. Сегодня я вам расскажу про другой метод решения этой проблемы и имя ему декоратор.

Давайте рассмотрим следующую функцию - декоратор, которая "оборачивает" другую функцию:

def log_decorator(function):
    def wrapped(*args):  # Вложенная функция-обёртка
        print("Log:", end=' ')
        function(*args)  # Вызываем нашу функцию с параметрами
    return wrapped 

А теперь давайте заменим наши функции на обёрнутые этой.

date_logger = log_decorator(date_logger)
connection_logger = log_decorator(connection_logger)

Действительно, теперь, при вызове будет выводиться "Log: ". Казалось бы, проблема решена. Но как-то не эстетично это выглядит. Поэтому в питоне есть специальный синтаксис для декорации:

@<имя декоратора>
def <декорируемая функция>(...):
	...

То есть наши функции будут выглядеть следующим образом:

@log_decorator
def date_logger():  # Выводит текущую дату и время
	print(datetime.now().strftime("%Y-%m-%d %H:%M:%S"))

@log_decorator
def connection_logger(connected): # Выводит статус соединения
	if connected:
		print("Connected")
	else:
		print("Not connected")

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

def word_log_decorator(word):
   def decorator(function):
       def wrapped(*args):
           print(word, end=': ')
           function(*args)
       return wrapped
   return decorator

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

@word_log_decorator("Log")
def date_logger():  # Выводит текущую дату и время
    print(datetime.now().strftime("%Y-%m-%d %H:%M:%S"))

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

Надеюсь, статья была вам полезной.

Надеюсь увидеть вас в моей группе SnakeBlog