Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

Замыкания Python: Функции

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

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

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

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

Когда возникают замыкания

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

Рассмотрим пример кода, чтобы проиллюстрировать это понятие:

def outer_function(x):
    def inner_function(y):
        return x + y
    return inner_function

closure = outer_function(10)
print(closure(5))  # => 15

В этом примере мы создаем функцию outer_function, которая принимает аргумент x и возвращает внутреннюю функцию inner_function. Внутренняя функция также принимает аргумент y и возвращает сумму x и y.

Затем мы создаем замыкание closure, вызывая outer_function с аргументом 10. Теперь closure ссылается на inner_function и хранит значение x как 10.

В конце вызываем closure с аргументом 5 и выводим результат — 15. Замыкание closure сохраняет значение x как 10 между вызовами, поэтому оно может быть использовано внутри inner_function даже после того, как outer_function уже завершила свою работу.

Scope

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

В Python есть две области видимости переменных:

  • Глобальная — относится к переменным, которые определены вне функций, классов или модулей. Если переменная определена в глобальной области видимости, она может быть использована в любом месте программы
  • Локальная — относится к переменным, которые определены внутри функций, классов или методов. Если переменная определена в локальной области видимости функции, то она не может быть использована вне этой функции

Рассмотрим пример:

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

def my_func():
    x = 5  # локальная переменная
    print("x внутри функции:", x)

my_func()
print("x вне функции:", x)

В этом примере у нас две переменные с именем x. Одна является глобальной переменной, определенной вне функции. А другая — локальная переменная, определенная внутри функции. Вывод этого кода будет следующим:

x внутри функции: 5
x вне функции: 10

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

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

x = 5
print(x) # => 5

def foo():
    global x
    x = 10
    print(x)

foo() # => 10
print(x) # => 10

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

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

Например:

def outer():
    x = 1
    def inner():
        nonlocal x
        x = 2
    print("outer:", x)
    inner()
    print("outer:", x)

outer()

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

  • outer — имеет переменную x со значением 1. Она выводит первоначальное значение x и затем вызывает функцию inner. После вызова inner она выводит новое значение x.
  • inner — устанавливает значение x равному 2, используя ключевое слово nonlocal.

При выполнении этого кода будет выведено:

outer: 1
outer: 2

Значение переменной x изменяется в функции inner с помощью ключевого слова nonlocal и это изменение также отражается в функции outer.

Как работают замыкания

Замыкание состоит из двух частей:

  • Внешняя функция
  • Внутренняя функция

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

Теперь рассмотрим несколько примеров замыканий:

def counter():
    count = 0
    def inner():
        nonlocal count
        count += 1
        return count
    return inner

c = counter()
print(c())  # 1
print(c())  # 2
print(c())  # 3

В этом примере мы создаем функцию counter, которая возвращает внутреннюю функцию inner. Внутри counter мы определяем переменную count и возвращаем inner. Внутренняя функция inner использует переменную count, которая определена во внешней функции counter с помощью оператора nonlocal, и увеличивает ее значение на единицу при каждом вызове.

Затем мы создаем замыкание c, вызывая counter. Замыкание c ссылается на inner и хранит значение count равное 0.

В конце вызываем c три раза и выводим результаты, которые должны быть 1, 2 и 3. Замыкание c сохраняет значение count между вызовами, поэтому переменная count увеличивается на единицу каждый раз, когда мы вызываем c.

Еще один пример:

def add_number(n):
    def inner(x):
        return x + n
    return inner

add_five = add_number(5)
add_ten = add_number(10)

print(add_five(3))  # 8
print(add_ten(3))   # 13

В этом примере мы создаем функцию add_number, которая принимает аргумент n и возвращает внутреннюю функцию inner. Внутренняя функция inner также принимает аргумент x и возвращает сумму n и x.

Затем мы создаем два замыкания: add_five и add_ten, вызывая add_number с аргументами 5 и 10 соответственно.

В конце вызываем каждое замыкание с аргументом 3 и выводим результаты, которые должны быть 8 и 13. Замыкания add_five и add_ten хранят значения n как 5 и 10 между вызовами, поэтому они могут быть использованы внутри inner_function даже после того, как add_number уже завершила свою работу.

Где применяются замыкания

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

Рассмотрим пример такой функции:

def password_protected(password):
    def inner():
        if password == 'secret':
            print("Access granted")
        else:
            print("Access denied")
    return inner

login = password_protected('secret')
login()  # Access granted

Здесь мы создаем функцию password_protected, которая возвращает внутреннюю функцию inner. Она проверяет, равен ли аргумент password, переданный при создании password_protected, 'secret', и выводит соответствующее сообщение.

Еще пример:

config = {
    'language': 'ru',
    'timezone': 'UTC'
}

def get_config(key):
    def inner():
        return config.get(key, None)
    return inner

get_language = get_config('language')
get_timezone = get_config('timezone')

print(get_language())  # ru
print(get_timezone())  # UTC

Здесь мы создаем словарь config и функцию get_config. Эта функция возвращает внутреннюю функцию inner, которая возвращает значение, связанное с переданным ключом. Затем мы создаем функции get_language и get_timezone с помощью get_config, чтобы получить значения из словаря config.

Выводы

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

Основные преимущества замыканий в Python:

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

Основные недостатки замыканий:

  • Могут привести к утечкам памяти, если они используются неправильно
  • Могут замедлить производительность, если используется большое количество замыканий

Дополнительные материалы

  1. Замыкания

Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Об обучении на Хекслете

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы
профессия
от 6 300 ₽ в месяц
Разработка веб-приложений на Django
10 месяцев
с нуля
Старт 4 апреля

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»