В pythonua@c.j.r xa4a поднял интересный вопрос о замыканиях в Python.
Код, ставший камнем преткновения:
l = []
for i in range(2):
for j in range(2):
l.append(lambda: i + j)
По идее, список l должен содержать анонимные функции, возвращающие 0, 1, 1, 2 (порядок в данном случае не важен).
Проверяем в ipython:
In [1]: l = []
In [2]: for i in range(2):
...: for j in range(2):
...: l.append(lambda: i + j)
In [3]: for x in range(4):
...: l[x]()
Out[3]: 2
Out[3]: 2
Out[3]: 2
Out[3]: 2
Облом.
Как одно из решений, была предложена конструкция следующего вида:
l = []
for i in range(2):
for j in range(2):
l.append(lambda i=i, j=j: i + j)
Проверяем.
In [3]: for x in range(4):
...: l[x]()
...:
...:
Out[3]: 0
Out[3]: 1
Out[3]: 1
Out[3]: 2
Работает.
Это пример простого и понятного всем неправильного решения.
Почему решение неправильное? Потому что в первом случае в список добавляются анонимные функции с 0 (нулем) аргументов и двумя свободными переменными. Во втором случае в список попадают уже анонимные функции от 2-х аргументов, для которых (аргументов) указаны значения по умолчанию. Для понятности второй вариант анонимной функции можно переписать так:
lambda n=i, m=j: n + m
То есть "мы шли на Одессу, а вышли к Херсону".
Перед тем, как рассмотреть правильное решение, рассмотрим почему получается то, что получается.
In [1]: l = []
In [2]: i = 3
In [3]: f = lambda: i + 3
In [4]: f()
Out[4]: 6
In [5]: i = 5
In [6]: f()
Out[6]: 8
Очевидно, что i в анонимной функции и i снаружи от нее -- смотрят в одно и то же место памяти, хотя я ждал, что i внутри функции переопределит i снаружи.
Что делать?
Правильное решение:
def lsum(n, m):
return lambda: n + m
l = []
for i in range(2):
for j in range(2):
l.append(lsum(i, j))
При использовании дополнительной функции значения i и j копируются, после её завершения ссылки на копии остаются только внутри анонимной функции, а циклы доступа к копии не имеют.
Проверяем:
In [4]: for x in range(4):
...: l[x]()
Out[4]: 0
Out[4]: 1
Out[4]: 1
Out[4]: 2
В PLT Scheme ситуация аналогичная:
(define i 3)
(define f (lambda () i))
(f)
3
(set! i 5)
(f)
5
но на фоне предпочтитения рекурсии циклам неожиданности возникают значительно реже.
Мораль придумайте сами.