Замыкания и Декораторы

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

Теория области видимости (Scope)

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

Область видимости — это часть программы, где переменная доступна. В Python есть четыре уровня области видимости, которые можно запомнить по аббревиатуре LEGB:

  1. L (Local scope) — локальная область видимости. Переменные, определённые внутри функции, доступны только внутри этой функции.
  2. E (Enclosing scope) — область видимости окружающей функции. Это относится к вложенным функциям, где внешняя функция может дать доступ внутренней функции к своим переменным.
  3. G (Global scope) — глобальная область видимости. Переменные, определённые на уровне модуля или программы, доступны во всех функциях этого модуля.
  4. B (Built-in scope) — встроенная область видимости. Это встроенные функции и константы, такие как len(), range(), print() и т.д.

Пример использования областей видимости:

x = "глобальная переменная"

def outer_function():
    y = "переменная внешней функции"
    
    def inner_function():
        z = "локальная переменная"
        print(x)  # доступ к глобальной переменной
        print(y)  # доступ к переменной внешней функции
        print(z)  # доступ к локальной переменной
    
    inner_function()

outer_function()

Результат:

глобальная переменная
переменная внешней функции
локальная переменная

В этом примере:

  • Переменная x имеет глобальную область видимости, она доступна везде.
  • Переменная y существует в enclosing области — это область внешней функции, доступная внутренней.
  • Переменная z — это локальная переменная функции inner_function.

Замыкания


Что такое замыкание?

Замыкание (closure) — это функция, которая "запоминает" свое окружение (переменные, доступные ей при создании) даже после того, как эта функция завершила свою работу и вышла из области видимости. То есть, когда вы вызываете такую функцию позже, она может использовать переменные, которые были в области видимости при её определении.

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

Пример замыкания:

def outer_function(message):
    def inner_function():
        print(f'Сообщение: {message}')
    return inner_function

# Создаём замыкание
closure = outer_function('Привет, мир!')

# Вызов замыкания
closure()  # Вывод: Сообщение: Привет, мир!

Разбор:

  • Внешняя функция outer_function принимает параметр message и возвращает внутреннюю функцию inner_function.
  • inner_function имеет доступ к переменной message, даже после того как outer_function завершила выполнение.
  • Переменная message запоминается и используется в замыкании, когда мы вызываем closure().

Почему это важно?

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

Рассмотрим пример счётчика:

def make_counter():
    count = 0
    
    def increment():
        nonlocal count  # доступ к переменной count из внешней функции
        count += 1
        return count
    
    return increment

counter = make_counter()

print(counter())  # Вывод: 1
print(counter())  # Вывод: 2
print(counter())  # Вывод: 3

Разбор:

  • Мы создаём функцию make_counter, которая возвращает внутреннюю функцию increment.
  • Переменная count запоминается, и каждый вызов counter() увеличивает её значение. Она сохраняет своё состояние благодаря замыканию.

Декораторы

Теперь, когда мы понимаем, как работают замыкания, перейдём к декораторам.


Что такое декораторы?

Декоратор — это функция, которая принимает другую функцию в качестве аргумента и модифицирует или дополняет её поведение, возвращая новую функцию.

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


Пример декоратора:

def decorator(func):
    def wrapper():
        print('Это код, который выполняется ДО вызова функции')
        func()
        print('Это код, который выполняется ПОСЛЕ вызова функции')
    return wrapper

# Пример использования декоратора
@decorator
def say_hello():
    print('Привет, мир!')

say_hello()

Разбор:

  • Мы определили декоратор decorator, который принимает функцию func и возвращает внутреннюю функцию wrapper.
  • В wrapper добавляется код до и после вызова func().
  • Декоратор можно применить к функции с помощью @decorator, и когда мы вызываем say_hello(), мы видим результат:
Это код, который выполняется ДО вызова функции
Привет, мир!
Это код, который выполняется ПОСЛЕ вызова функции

Применение декораторов


Логгирование

Декораторы могут использоваться для ведения логов. Например, вы можете отслеживать время выполнения функции:

import time

def timer(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f'Функция {func.__name__} выполнена за {end - start:.5f} секунд')
        return result
    return wrapper

@timer
def calculate_sum(n):
    return sum(range(n))

calculate_sum(1000000)

Разбор:

  • Декоратор timer оборачивает любую функцию, измеряя время её выполнения.
  • Мы вызываем calculate_sum(1000000) и видим, сколько времени понадобилось для выполнения функции.


Авторизация

Другой пример использования — проверка прав пользователя при доступе к функции:

def requires_permission(user_role):
    def decorator(func):
        def wrapper(*args, **kwargs):
            if user_role != 'admin':
                print('Доступ запрещён')
                return
            return func(*args, **kwargs)
        return wrapper
    return decorator

@requires_permission('user')
def delete_database():
    print('База данных удалена')

delete_database()  # Вывод: Доступ запрещён

Разбор:

  • Декоратор requires_permission проверяет, обладает ли пользователь правами для выполнения функции.
  • В данном примере пользователь не имеет прав, и выполнение функции блокируется.

 

Вот таблица с основными понятиями из лекции о области видимости, замыканиях и декораторах:

ПонятиеОписаниеПример кодаРезультат
Область видимости (Scope)Часть программы, где переменная доступна. В Python есть 4 уровня области видимости: LEGB.  
L (Local scope)Локальная область. Переменные, определённые внутри функции, доступны только внутри неё.def inner(): z = "локальная переменная"z доступна только в функции inner.
E (Enclosing scope)Область окружающей функции. Переменные внешней функции доступны внутренней функции.def outer(): y = "внешняя"; def inner(): print(y)Внутренняя функция имеет доступ к переменной y.
G (Global scope)Глобальная область. Переменные, определённые на уровне модуля, доступны везде в модуле.x = "глобальная"x доступна в любой функции этого модуля.
B (Built-in scope)Встроенная область. Встроенные функции и константы Python, например, len(), print().print(len("строка"))Вызов встроенной функции len() из любой части программы.
Замыкание (Closure)Функция, которая "запоминает" переменные внешней функции даже после её завершения.def outer(msg): def inner(): print(msg); return innerФункция inner помнит переменную msg даже после завершения outer.
ДекораторФункция, которая изменяет поведение другой функции без изменения её кода.@decorator def say_hello(): print("Привет!")Модифицированная функция добавляет действия до и после вызова.
Логгирование через декораторИзмерение времени выполнения функции с помощью декоратора.@timer def calc_sum(n): return sum(range(n))Логирует время выполнения функции calc_sum.
Авторизация через декораторДекоратор проверяет права пользователя перед выполнением функции.@requires_permission('user') def delete_db():Ограничивает доступ для пользователей без прав.

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

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

Перейти к следующему шагу

Комментарии