В проекте Yatube вы настроили авторизацию, и теперь надо изменить код проекта так, чтобы определённые функции сайта были доступны только авторизованным пользователям: например, у неавторизованных пользователей не должно быть возможности создать пост, подписаться на автора или отправить комментарий. Для этого некоторые view-функции должны проверять авторизацию:
from django.shortcuts import redirect
...
def only_user_view(request):
if not request.user.is_authenticated:
# Если пользователь не авторизован - отправляем его на страницу логина.
return redirect('/auth/login/')
# Если пользователь авторизован — здесь выполняется полезный код функции.
Такая проверка должна быть в каждой view-функции, работающей с пользовательскими страницами. Таких функций может быть достаточно много, и в каждой придётся повторять один и тот же код. Это нарушает принцип DRY, загромождает код и усложняет внесение изменений: при рефакторинге придётся править однотипный код в десятке функций.
Для решения подобных задач в Python применяют декораторы (иначе его называют обёртка, англ. «wrapper«).
Декоратор — это паттерн проектирования, предназначенный для расширения функциональности объектов без вмешательства в их код. Именно декораторы позволят добавить проверку авторизации к любым view-функциям.
Чтобы понять принцип работы декораторов — нужно разобраться с понятиями «область видимости» и «замыкание».
Глобальная и локальная область видимости
global_variable = 'Глобальная'
def some_func(passed_variable):
local_variable = 'Локальная'
return(f'{global_variable} '
f'{local_variable} '
f'{passed_variable}')
print(some_func('Параметр'))
# Будет напечатано: Глобальная Локальная Параметр
При попытке вызвать local_variable
из глобальной области видимости появится сообщение об ошибке:
NameError: name 'local_variable' is not defined
Вызываемая переменная определена в локальной области видимости some_func()
и будет удалена сборщиком мусора после того, как функция завершит работу.
Функция some_func()
возвращает строку, сформированную из значений трёх переменных.
- Переменная
global_variable
объявлена вне этой функции, в глобальной области видимости (англ. global scope). - Переменная
passed_variable
передана в функцию в качестве параметра. - Переменная
local_variable
объявлена внутри функции, в локальной области видимости (англ. local scope).
Все три переменные доступны внутри функции (даже global_variable
, которая не имеет к функции никакого отношения).
Функция может использовать переменную, объявленную во внешней области видимости, даже если эта переменная явно не передана в функцию. Обратиться к локальной переменной из глобальной области видимости не получится.
Вложенные функции
Объявим функцию внутри функции и вызовем эту вложенную функцию внутри объемлющей.
global_variable = 'Глобальная'
def some_func(passed_variable):
local_variable = 'Локальная'
def inside_func():
inside_local_variable = 'Внутренняя'
return(f'{global_variable} '
f'{local_variable} '
f'{passed_variable} '
f'{inside_local_variable}')
print(inside_func())
some_func('Параметр')
# Будет намечатано: Глобальная Локальная Параметр Внутренняя
При попытке вызвать inside_func()
из глобальной области появится сообщение об ошибке:
NameError: name 'inside_func' is not defined.
Функция inside_func()
доступна только в области видимости some_func()
.Переменная local_variable
объявлена внутри функции some_func()
, но вне вложенной функции inside_func()
. Переменные, объявленные в объемлющей функции, для вложенной функции находятся в области видимости enclosing scope («объемлющая» или «контекстная» область видимости).У вложенных функций есть доступ к переменным в enclosing scope (независимо от уровня вложенности) и к глобальным переменным. Вызвать вложенную функцию можно только из её области видимости.
Функция возвращает функцию
Вложенную функцию inside_func()
можно не только вызывать внутри some_func()
(как это было в примере выше), но и вернуть её, как результат выполнения объемлющей функции. Возвращаемое значение можно присвоить переменной:
global_variable = 'Глобальная'
def some_func(passed_variable):
local_variable = 'Локальная'
def inside_func():
inside_local_variable = 'Внутренняя'
return (f'{global_variable} '
f'{local_variable} '
f'{passed_variable} '
f'{inside_local_variable}')
return inside_func
# Здесь вызывается функция some_func()
# и результат её работы присваивается переменной kind_of_magic
kind_of_magic = some_func('Параметр')
# Здесь рождается магия: some_func() вернула функцию,
# значит, kind_of_magic — это функция
# и её можно вызвать:
print(kind_of_magic())
# Будет напечатано: Глобальная Локальная Параметр Внутренняя
# Можно создать ещё одну функцию
another_magic = some_func('Другой параметр')
print(another_magic())
# Будет напечатано: Глобальная Локальная Другой параметр Внутренняя
kind_of_magic()
и another_magic()
— это функции в глобальной области видимости, которым доступны переменные из enclosing scope функции inside_func()
. Каждая из этих функций — вполне самостоятельна и будет работать, даже если после создания этих функций уничтожить «родительскую» функцию some_func()
:
global_variable = 'Глобальная'
def some_func(passed_variable):
local_variable = 'Локальная'
def inside_func():
inside_local_variable = 'Внутренняя'
return(f'{global_variable} '
f'{local_variable} '
f'{passed_variable} '
f'{inside_local_variable}')
return inside_func
# Создали две функции
kind_of_magic = some_func('Параметр')
another_magic = some_func('Другой параметр')
del some_func # Уничтожили функцию some_func()
# Всё равно сработает
print(kind_of_magic())
# Будет напечатано: Глобальная Локальная Параметр Внутренняя
print(another_magic())
# Будет напечатано: Глобальная Локальная Другой параметр Внутренняя
# А это уже не сработает:
# some_func() удалена, создать новую функцию не получится.
misery_of_magic = some_func('Пробуем последний раз')
# NameError: name 'some_func' is not defined
Функция some_func()
— это, по факту, своеобразная фабрика для создания и настройки копий функции inside_func()
.После удаления some_func()
вся её контекстная область видимости будет сохранена, потому что на эту область видимости существуют ссылки в объектах kind_of_magic
и another_magic
.
Вложенная функция принимает аргументы
Вложенная функция может принимать и обрабатывать аргументы.
def some_func(passed_variable):
def inside_func(passed_inside_variable):
return f'{passed_variable}, {passed_inside_variable}'
return inside_func
# Создали функцию hello()
hello = some_func('Привет')
# В контексте функции hello() значением passed_variable будет строка 'Привет'
# Вызываем hello() с параметром для inside_func()
print(hello('Стёпа'))
# Будет напечатано: Привет, Стёпа
byebye = some_func('До свидания')
# В контексте функции byebye()
# значением passed_variable будет строка 'До свидания'
print(byebye('Марк Лутц'))
# Будет напечатано: До свидания, Марк Лутц
# Но значение переменной passed_variable в контексте hello() сохранилось:
# оно по-прежнему 'Привет'
print(hello('Лера'))
# Будет напечатано: Привет, Лера
Замыкание (closure) — это способность вложенной функции запоминать локальное состояние контекстной области объемлющей функции.
С помощью замыкания на основе some_func()
были созданы функции hello()
и byebye()
, каждая со своей уникальной контекстной областью для inside_func()
.
ФУНКЦИЯ | ЗНАЧЕНИЕ ПЕРЕМЕННОЙ PASSED_VARIABLE В КОНТЕКСТНОЙ ОБЛАСТИ ФУНКЦИИ INSIDE_FUNC() |
---|---|
hello() | ‘Привет’ |
byebye() | ‘До свидания’ |
У замыканий может быть и более сложное поведение:
# В зависимости от уровня сложности (volume)
# функция возвращает различное написание фразы.
def speech(text, volume):
def whisper():
return f'{text.lower()}...'
def scream():
return f'{text.upper()}!!!11'
if volume < 50:
return whisper
return scream
easy_closure = speech('Замыкание - это просто', 99)
print(easy_closure())
# Будет напечатано: ЗАМЫКАНИЕ - ЭТО ПРОСТО!!!11