Вступление

Функции в Python - это граждане первого класса. Это означает, что мы можем управлять функциями как другими объектами:

  • Передавать функции как аргументы
  • Назначьте функцию переменной
  • Вернуть функцию из другой функции (вложенные функции)

На их основе Python поддерживает мощную технику: закрытие.

Большинство из нас слышали о закрытии, но полностью понять его и правильно использовать непросто.

К счастью, этот пост слой за слоем раскроет тайну закрытия. Пришло время приготовить чашку кофе и насладиться этой техникой. ☕️

Уровень 0: понять, что такое закрытие

Замыкание - это концепция в контексте вложенных функций. Давайте посмотрим на это на интересном примере:

Приведенный выше пример может быть очень запутанным, если вы еще не знаете закрытия. Мы уже удалили функцию outer_func. Однако f() все еще может печатать «Ян Чжоу», которая является локальной переменной функции outer_func. Почему его локальная переменная все еще жива после удаления функции?

Очевидно, это не потому, что Ян Чжоу - волшебник. Это эффект техники закрытия.

С функциональной точки зрения замыкание - это запись, в которой функция хранится вместе с окружением. (Википедия)

Что касается нашего примера, внутренняя функция print_leader может «запоминать» переменные во внешней функции.

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

Уровень 1: отличать замыкания от вложенных функций

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

  1. Есть вложенные функции.
  2. Внутренняя функция должна использовать переменные, определенные во внешней функции.
  3. Внешняя функция должна возвращать внутреннюю функцию.

Давайте немного изменим предыдущий пример:

Как показано выше, если мы вернем результат внутренней функции. Никаких закрытий не произойдет. f - это NoneType, поскольку он не получил функцию print_leader, функция print_leader уже выполнена.

Уровень 2. Знайте, как получить заключенные ценности

Фактически, каждая функция в Python имеет специальный атрибут __closure__, в котором хранятся все «запомненные» значения.

Как показано в приведенном выше примере, outer_func не является закрытием, а его атрибут __closure__ равен None. С другой стороны, __closure__ из f содержит объект ячейки, который сохраняет запомненное значение.

Уровень 3. Реализуйте закрытие без ошибок

Замыкания реализовать непросто, и мы должны использовать их осторожно.

Отрицательный пример

Давайте посмотрим на пример программы, которая сохранит три функции в list и запустит их одну за другой для вывода трех разных четных чисел:

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

Просмотрите код еще раз, и мы увидим, что заключенная переменная i уже была равна 2, когда мы действительно запускали f1(), f2() и f3() в методе print(). Если мы напечатаем их __closure__ атрибуты, результаты будут следующими:

Вопрос на собеседовании

Основываясь на приведенном выше примере, вот хороший вопрос для собеседования:

Как изменить код для печати трех разных значений разными функциями?

Остановись на две минуты и подумай!

Решение следующее:

Как показано выше, мы используем переменную j для получения параметра i, и результаты такие, как ожидалось! Однако на этот раз три функции не являются замыканиями, поскольку им не нужно «запоминать» какие-либо переменные в их охватывающей области.

Вспомните второе условие из 3 условий для создания замыкания, упомянутого на уровне 1:

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

В приведенном выше примере переменная i не использовалась во внутренней функции. Он просто передал значение как аргумент параметру j внутренней функции. Таким образом, закрытие не было создано.

Урок, извлеченный из приведенных выше примеров

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

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

Замыкание - продвинутое оружие Python. Для новичков это может быть немного сложно. Однако, если мы сможем полностью понять это и умело использовать, это нам очень поможет. На самом деле декораторы в Python - это просто обширные приложения замыканий.

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

Используйте лямбда-функцию для упрощения кода

Мы можем сделать предыдущий пример кода более элегантным с помощью лямбда-функции:

Замыкания более эффективно скрывают частные переменные

В Python нет встроенных ключевых слов, таких как public или private, для управления доступностью переменных. По соглашению мы используем двойное подчеркивание для определения закрытого члена класса. Но доступ к частной переменной все еще возможен.

Иногда нам нужна более сильная защита скрытой переменной. Замыкания могут помочь!

Как показано в example6.py, получить и изменить значение переменной leader в функции f сложнее. Переменная leader более «закрытая».

Заключение

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

Спасибо за внимание! Относительные сообщения: