категории | RSS

Замыкания:
Суть замыкания заключается в том, что любой объект при своём создании способен "запоминать" состояние окружающей среды, как бы "замыкаясь" на нём.
Поскольку состояние среды, то бишь пространства имён, характеризуется переменными, вернее значениями переменных (ну, совсем тру, объектами, на которые ссылаются переменные), а глобальное пространство имён всегда одно и то же, то замыкание проявляется в контексте локальных пространств имён. Причём в пр. имён класса замыкание не работает, например:
>>> 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
>>>
Как это реализовать? Чем необычнее будет решение, тем лучше smile.
Внимание: данный пост не окончен и будет дополняться в течении недели.

Virtuos86
2011-08-08T10:10:45Z

Здесь находятся
всего 0. За сутки здесь было 0 человек

Комментарии 8

#8   dimy44    

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


0 ответить

#8   Virtuos86    

Для затравки мои варианты ответов:
1.) x и y несомненно могут быть определены ранее, иначе задача была бы тривиальна.
Я неспроста упомянул об \"утиной\" типизации.
На самом деле эта строка может привести к выполнению произвольного кода.
а.) Во-первых, если x и y числа, то вычислится остаток от деления x на y:

>>> 5 % 3.
2
>>> 5 % 3.
2.0
> 0xa75 % 3.
1.0
>>> 075 % 3.
1.0
>>> (5-3j) % 2 # если честно, я был уверен, что комплексные числа не поддерживают такую операцию; так оно и оказалось, зато ворнинг довольно любопытный
C:\\private\\e9e58be2\\default.py:1: DeprecationWarning: complex divmod(), // and % are deprecated
try:
(1-3j)
>>>

б.) Во-вторых, не будем забывать, что \'%\' - это ещё
(если даже не прежде всего по используемости) и оператор форматирования строки:

>>> x = \'%s\'
>>> class Y(object):
... def __str__(self):
... print \'I am redefined \"__str__\"-method.\'
... return object.__str__(self)
...
>>> y = Y()
>>> x % y
I am redefined \"__str__\"-method.
\'\'
>>>

Стоит ли говорить, что можно засунуть в такой переопределенный метод __str__ произвольный код, пусть хоть запускающий ракету на Луну?
в.) В-третьих, возвращаясь к первому пункту возможен и такой вариант:

>>> class X(int):
... def __mod__(self, other):
... print \'I am custom \"__mod__\"-method\'
...
>>> x = X()
>>> y = 5 # всё, что угодно
>>> x % y
I am custom \"__mod__\"-method
>>>

\"Если что-то выглядит как утка, крякает как утка и ходит как утка, то скорее всего это утка. Гусь? Вполне возможно.\"

2.) Хм, я полагал, что имеется простое и элегантное решение, но оказалось это не так. Простое есть, уже предложено выше, элегантное что-то не получается smile.
Ничего страшного, всё только начинается...


0 ответить

#8   dimy44    

Че-то я в первый вопрос не въехал. Если буквально, то NameError получим и все. А так остаток от деления, если числа... Но не это ж имелось ввиду?
-------------
Добавлено в 17.31: Вот первое, что в голову пришло
> class foo:
class L:
Python = \"I like Python\"
like = L()

> I = foo()
> I.like.Python
\'I like Python\'
>
-------------
Добавлено в 18.00: Столько просмотров, и никто своих вариантов не пишет. Я так не играю =)
-------------
Добавлено в 18.28: Столько просмотров, и никто своих вариантов не пишет. Я так не играю =)


0 ответить

#8   Virtuos86    

smallnad,
О том как не писать на Python \"прожки\", а программировать осознанно, умело и со знанием дела.


0 ответить

#8   smallnad    

Пацаны, вы о чем?


0 ответить

#8   dimy44    

Хм. А я считал, что \"замороженная\" в функции переменная это тоже замыкание...


0 ответить

#8   Virtuos86    

dimy44
Это не совсем замыкание. Это аргументы по умолчанию. Разница в том, что:
1) В отличие от замыкания указываются явно.
2) Аргументам по умолчанию можно задавать новые значения при вызове функции.


0 ответить

#8   dimy44    

Или так:
> x = 5
> def func(arg=x):
print '%d == %d' %(arg, x) #раз arg=x, казалось бы, мы вправе так написать? ;)

> func()
5 == 5
> x = 10
> func()
5 == 10 #а где 10? =)


* редактировал(а) dimy44 10:53 8 авг 2011

0 ответить

Яндекс.Метрика