Замыкания:
Суть замыкания заключается в том, что любой объект при своём создании способен "запоминать" состояние окружающей среды, как бы "замыкаясь" на нём.
Поскольку состояние среды, то бишь пространства имён, характеризуется переменными, вернее значениями переменных (ну, совсем тру, объектами, на которые ссылаются переменные), а глобальное пространство имён всегда одно и то же, то замыкание проявляется в контексте локальных пространств имён. Причём в пр. имён класса замыкание не работает, например:
>>> bar = 2
>>> class Foo(object):
... bar = 5
... def func(): print bar
... func()
...
>>> 2 # а где 5?
>>> def foo():
... bar = 5
... def func(): print bar
... func()
...
>>> foo()
5
>>>
Не могу не привести популярного сравнения:
'''Если объекты это данные, с прикрепленными к ним функциями, то замыкания - это функции, с прикрепленными к ним данными.'''.
Оно мало, что объясняет, но по сути верно.
Как ни разливайся соловьем, а теорией всего не объяснишь, поэтому перейдем к практике.
По крайней мере из неё в любом случае можно вынести пользу.
=================
На использовании замыканий построен такой популярный приём программирования как `декораторы`. Декораторы или украшатели позволяют дополнить функциональность вызываемых (callable) объектов.
Чтобы избежать излишней сложности я не буду говорить обо всех выз. объектах, а ограничусь функциями.
Что есть функция?
Функцию можно рассматривать либо как математическую функцию, то есть отображение множества области определения D(f) во множество области значения E(f), либо как процедуру (подпрограмму).
В первом случае представляют интерес как входные параметры функции, так и результат её выполнения. Во втором случае результат выполнения как правило не представляет никакого интереса, поскольку подпрограммы в общем случае не обязаны ничего возвращать.
Наиболее интересны для декорирования функции, удовлетворяющие первому определению.
Залезть внутрь функции и изменить в ней строчку кода мы не можем, но мы можем контролировать входные данные и результат.
Что нам это даёт?
Приведу пример и заодно покажу как же выглядят эти декораторы в действии:
def logger(f, output):
def wrapper(*a, **kwa):
result = f(*a, **kwa)
output.write('\n'.join((f.func_name, repr(map(repr, a)), repr(kwa.items()), repr(result))))
return result
return wrapper
def foo(a, b=3):
...
return
output = open('d:/output.txt', 'w')
foo = logger(foo, output)
foo("2", b=5)
Итак пойдем шаг за шагом.
Имеется некая функция `foo`
def foo(a, b=3):
...
return
И такая странная: то работает как надо, то выдает какую-то ерунду.
А ерунда нам не нужна, мы допустим её (функции) "выхлоп" показываем пользователю, и опасаемся его справедливого негодования от лицезрения всяческой несуразицы.
И по этой причине нам жутко, ну до нельзя потребовалось узнать досконально, что же эта злополучная функция получает на завтрак, обед и ужин, и чем, пардон, она какает.
На помощь мог бы прийти могучий print, но мы-то опытные джедаи и сразу смекнули, что таковая задачка может возникнуть не раз и не два, и хорошо бы иметь что-то получше, чем умение расставлять print'ы направо и налево.
Да и мало, ли что. Вдруг мы как ни бились, не смогли добиться проявления описанной выше ошибки, а у юзер'а она регулярно проявляется. Юзеры они знаете ли такие, талантливые, в общем.
В общем, почесав лоб, выдали мы на-гора такой вот чудо-декоратор:
def logger(f, output):
def wrapper(*a, **kwa):
result = f(*a, **kwa)
output.write('\n'.join((f.func_name, repr(map(repr, a)), repr(kwa.items()), repr(result))))
return result
return wrapper
Декоратор в общем случае принимает на вход функцию, и возвращает что-то, что умело прикидывается этой функцией. А то, что он помимо этого может ещё много чего делать, это никого не волнует.
Итак наш декоратор `logger` принимает любую функцию, а возвращает универсальный wrapper - `обертку` - вложенную функцию-замыкание, которая, несмотря на уничтожение контекста своего создания (то, что локальное пространство функции `logger` после её выполнения будет уничтожено, надеюсь, никому не нужно объяснять?), "запомнит" функцию `f`.
Универсальность wrapper заключается, конечно, в том, что она в состоянии принимать произвольное количество позиционных и именнованных аргументов, а значит соответствовать входному интерфейсу любой функции.
Рассмотрим повнимательнее код wrapper:
def wrapper(*a, **kwa):
# Ага, при вызове wrapper мы вызывем "замкнутую" функцию f и получим результат её выполнения.
result = f(*a, **kwa)
# Далее мы пишем в некий файлоподобный объект `output`, о котором ранее умолчали, имя функции, аргументы функции и результат её выполнения.
output.write('\n'.join((f.func_name, repr(map(repr, a)), repr(kwa.items()), repr(result))))
# Возвращаем результат выполнения функции f.
return result
Итак, если в logger передать некую функцию, то он вернет функцию, которая сымитирует работу переданной функции, и попутно выполнит запись лога вызова.
Слушайте, довольно неплохое "украшение" первоначальной функции, не так ли?
Теперь нужно только заменить изначальную функцию на её декорированный вариант
output = open('d:/output.txt', 'w') # указав sys.stderr, получим вывод в консоль
foo = logger(foo, output)
и отдать юзеру на испытание, попросив при возникновении ошибок присылать вам указанный выше генерируемый файл.
При этом заметьте в код самой функции `foo` не внесено ни единой правки, а весь этот дебаггинг отключается закомментированием пары строчек.
Надеюсь мои скромные усилия по созданию потока внятной и доходчивой информации не пропали втуне.
Это всё ещё не окончательная версия поста.
Ждите правок и дополнений.
[ ==================== ]
Задачки для ума:
1.) Какой результат будет получен при выполнении следующей строки кода? Помните об утиной типизации.
>>> x % y
2.)
>>> I = foo()
>>> I.like.Python
I like Python
>>>
Как это реализовать? Чем необычнее будет решение, тем лучше

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