closures

Contents
Введение
Пример
Пример записи в лог
__closure__
Фабрики функций
Области видимости
Похожие статьи

Введение

Нужно предварительно изучить тему функции первого класса

Функции, которые определены внутри других функций называются локальными функциями (local functions)

Они определены после того как выполнен их def .

Новая копия объекта функции создаётся при каждом вызове. Отдельная связь имя - объект функции каждый раз.

Локальные функции не являются членами (member) родительских функций.

Вызов parent.local() не сработает .

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

Говоря другим языком, замыкание — функция, которая ссылается на свободные переменные в своей области видимости.

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

Замыкание — это особый вид функции. Она определена в теле другой функции и создаётся каждый раз во время её выполнения.

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

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

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

Замыкание связывает код функции с её лексическим окружением (местом, в котором она определена в коде). Лексические переменные замыкания отличаются от глобальных переменных тем, что они не занимают глобальное пространство имён. От переменных в объектах они отличаются тем, что привязаны к функциям, а не объектам.

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

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

Пример

Создайте файл closures.py

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

def outer_func(): message = 'Hi' def inner_func(): print(message) return inner_func() outer_func()

python closures.py

Hi

Изменим код

def outer_func(): message = 'Hi' def inner_func(): print(message) return inner_func outer_func()

python closures.py

Вывода 'Hi' не произошло, так как внутренняя функция не исполняется а только возвращается

ef outer_func(): message = 'Hi' def inner_func(): print(message) return inner_func my_func = outer_func() print(my_func) print(my_func.__name__) my_func()

python closures.py

<function outer_func.<locals>.inner_func at 0x7f6b693d9a60> inner_func Hi

def outer_func(msg): message = msg def inner_func(): print(message) return inner_func hi_func = outer_func('Hi') hello_func = outer_func('Hello') print(hi_func) print(hi_func.__name__) hi_func() print(hello_func) print(hello_func.__name__) hello_func()

python closures.py

<function outer_func.<locals>.inner_func at 0x7fce92122a60> inner_func Hi <function outer_func.<locals>.inner_func at 0x7fce92122af0> inner_func Hello

Укоротить код можно убрав message

def outer_func(msg): def inner_func(): print(msg) return inner_func hi_func = outer_func('Hi') hello_func = outer_func('Hello') print(hi_func) print(hi_func.__name__) hi_func() hello_func()

Пример

Рассмотрим пример применения замыкания для записи логов. Про модуль logging читайте здесь

import logging logging.basicConfig(filename='example.log', level=logging.INFO) def logger(func): def log_func(*args): logging.info('Running "{}" with arguments {}'.format(func.__name__, args)) print(func(*args)) return log_func def add(x, y): return x+y def sub(x, y): return x-y add_logger = logger(add) sub_logger = logger(sub) add_logger(3, 3) add_logger(4, 5) sub_logger(10, 5) sub_logger(20, 10)

python closures.py

6 9 5 10

cat example.log

INFO:root:Running "add" with arguments (3, 3) INFO:root:Running "add" with arguments (4, 5) INFO:root:Running "add" with arguments (3, 3) INFO:root:Running "add" with arguments (4, 5) INFO:root:Running "sub" with arguments (10, 5) INFO:root:Running "sub" with arguments (20, 10)

Пример

Сортировка строк по последнему символу

def sort_by_last_letter(strings): def last_letter(s): return s[-1] return sorted(strings, key=last_letter) cities = ["Helsinki", "Malaga", "Torremolinos"] sorted_cities = sort_by_last_letter(cities) print(sorted_cities)

['Malaga', 'Helsinki', 'Torremolinos']

Внесём в функцию sort_by_last_letter() небольшое изменение, чтобы убедиться в том, что каждый раз создаётся новый объект.

def sort_by_last_letter(strings): def last_letter(s): return s[-1] print(last_letter) return sorted(strings, key=last_letter) cities = ["Helsinki", "Malaga", "Torremolinos"] sorted_cities = sort_by_last_letter(cities) print(sorted_cities)

g = "global" def outer(p="param"): l = "local" def inner(): print(g, p, l) inner() outer()

global param local

python
outer.inner()

Traceback (most recent call last): File "/home/andrei/python/outer.py", line 12, in <module> outer.inner() AttributeError: 'function' object has no attribute 'inner'

__closure__

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

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

Это получается с помощью замыканий.

Объекты из окружающей области видимости записываются и сохраняются после того как она исчезла. Эти объекты защищены от сборщика мусора.

Реализованы замыкания с помощью атрибута __closure__ в котором сохранены ссылки на необходимые объекты.

def enclosing(): x = 'closed over' def local_func(): print(x) return local_func lf = enclosing() lf() print(lf.__closure__)

python enclosing.py

closed over (<cell at 0x7f5ec5f47be0: str object at 0x7f5ec5eba970>,)

Видно, что x, который был в области видимости родительской функции, сохранён в атрибут __closure__ локальной функции, которая была возвращена.

Фабрики функций

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

Некая комбинация runtime function definition и замыкания.

Рассмотрим фабрику функций по возведению числа в заданную степень.

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

def raise_to(exp): def raise_to_exp(x): return pow(x, exp) return raise_to_exp square = raise_to(2) print(square.__closure__) print(square(5)) # 25 print(square(11)) # 121 cube = raise_to(3) print(cube.__closure__) print(cube(3)) # 27 print(cube(10)) # 1000

python3 raise_to.py (<cell at 0x7f7cf6e01be0: int object at 0x958e40>,) 25 121 (<cell at 0x7f7cf6e01bb0: int object at 0x958e60>,) 27 1000

Области видимости

Продолжим обсуждение областей видимости переменных на примере замыканий.

message = 'global' def enclosing(): message = 'enclosing' def local(): message = 'local' print('enclosing message:', message) local() print('enclosing message:', message) print('global message:', message) enclosing() print('global message:', message)

python3 scopes.py

global message: global enclosing message: enclosing enclosing message: enclosing global message: global

Вызов enclosing() и local() не изменяет message, так как внутри local() создаётся новая связь (binding) между именем и телом функции

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

message = 'global' def enclosing(): message = 'enclosing' def local(): global message message = 'local' print('enclosing message:', message) local() print('enclosing message:', message) print('global message:', message) enclosing() print('global message:', message)

python3 scopes.py

global message: global enclosing message: enclosing enclosing message: enclosing global message: local

Чтобы из функции local() изменить переменную из enclosing(), которая не является ни локальной ни глобальной, нужно ключевое слово nonlocal.

nonlocal будет искать соответсвие "снизу вверх", то есть, минуя локальные переменные, пойдёт искать в Enclosing и если не найдёт там, то в глобальные. Использоваться будет первое найденное совпадение.

message = 'global' def enclosing(): message = 'enclosing' def local(): nonlocal message message = 'local' print('enclosing message:', message) local() print('enclosing message:', message) print('global message:', message) enclosing() print('global message:', message)

python3 scopes.py

global message: global enclosing message: enclosing enclosing message: local global message: global

Рассмотрим ещё один пример применения nonlocal

import time def make_timer(): last_called = None # Never def elapsed(): nonlocal last_called now = time.time() if last_called is None: last_called = now return None result = now - last_called last_called = now return result return elapsed t = make_timer() print(t()) print(t()) print(t())

python make_timer.py

None 2.2411346435546875e-05 1.5735626220703125e-05

elapsed() использует nonlocal переменную last_called для того чтобы хранить там данные между вызовами elapsed().

Каждый новый вызов make_timer() создает новое присваивание значения (new binding) last_called.

Для демонстрации этой особенности добавим задержку в одну секунду и сделаем два объекта функции.

import time def make_timer(): last_called = None # Never def elapsed(): time.sleep(1) nonlocal last_called now = time.time() if last_called is None: last_called = now return None result = now - last_called last_called = now return result return elapsed t1 = make_timer() t2 = make_timer() print("t1():", t1()) print("t1():", t1()) print("t2():", t2()) print("t1():", t1()) print("t1():", t1()) print("t1():", t1()) print("t1():", t1()) print("t1():", t1()) print("t2():", t2()) print("t2():", t2()) print("t1():", t1())

python make_timer.py

t1(): None t1(): 1.0011835098266602 t2(): None t1(): 2.002288579940796 t1(): 1.0011866092681885 t1(): 1.0011425018310547 t1(): 1.0012156963348389 t1(): 1.0011701583862305 t2(): 6.0070576667785645 t2(): 1.0012218952178955 t1(): 3.0036113262176514

Видно, что вызовы t1() и t2() не влияют друг на друга.

Related Articles
*args **kwargs
Лямбда функции
all()
any()
map()
zip()
sorted()
Функции первого класса
Замыкания
Декораторы
Кэширование
Python
if, elif, else
Циклы
Методы
enum

Search on this site

Subscribe to @aofeed channel for updates

Visit Channel

@aofeed

Feedbak and Questions in Telegram

@aofeedchat