Я не совсем знал, что искать, когда разместил этот вопрос. Оказывается, на него (косвенно) ответили в ряде других сообщений, которые я собираю здесь в качестве справки. Это помогло мне понять, что происходит (даже несмотря на то, что «почему» и «как именно» все еще очень загадочны):
Для следующего обсуждения следующий пример кода поучительнее и проще, чем мой исходный вопрос:
funcs = []
for x in range(0, 3):
funcs.append(lambda: x)
for f in funcs:
print(f())
x = 5
for f in funcs:
print(f())
- 2
- 2
- 2
- 5
- 5
- 5
Чтобы еще больше взорвать мой / ваш мозг, учтите, что приведенный выше пример кода все еще работает, если вы связываете все в функции:
funcs = []
def foo(i):
for x in range(i, i+3):
funcs.append(lambda: x)
foo(0)
for f in funcs:
print(f())
- 2
- 2
- 2
Даже если переменная e интуитивно должна исчезнуть после выхода из функции (это означает, что даже если лямбда-выражения искали e в локальном пространстве имен - ну, это пространство имен больше не существует).
Хотя я знаю, что это не остановит будущих любопытных людей, таких как я, позвольте мне высказать свое мнение о том, что попытка понять, что здесь происходит, - гораздо больше проблем, чем того стоит. Мой вывод из нескольких часов поиска в Google и чтения заключается в том, что я неизбежно испорчу любую попытку сохранить лямбды (явно или неявно, как указано выше), и что попытка сделать это приведет к неожиданному поведению и (невероятно) сложному поведению для расшифровать.
https://www.python.org/dev/peps/pep-0227/
Почему python вложенные функции не называются замыканиями?
Что такое ячейка в контексте интерпретатора или компилятора?
Изменить связанные переменные замыкания в Python
https://eev.ee/blog/2011/04/24/gotcha-python-scoping-closures/
Как работают лексические замыкания?
В чем разница между ранним и поздним связыванием?
http://calculist.blogspot.com/2006/05/late-and-early-binding.html
Почему результаты map () и понимания списка различаются?
http://lambda-the-ultimate.org/node/2648
AttributeError: объект 'function' не имеет атрибута 'func_name' и python 3
http://zetcode.com/python/python-closures/
My bri ef (возможно, совершенно неверно) краткое изложение того, что происходит (надеюсь, это, по крайней мере, дает вам мысленную модель для рассуждений):
В приведенной выше функции foo, когда мы создаем лямбду, она не сохраняет значение x (как, например, в SML), а скорее x
. Когда позже вызывается лямбда, x
ищется в локальном пространстве имен (как и любой обычный поиск переменных).
Это то, что мы видим в примере 2. x
сохраняется в пространстве имен и может быть изменено, что изменяет будущие вызовы лямбда-выражений.
Пример 2 еще сложнее (ура!) . Поскольку переменная x
исчезает после завершения foo, можно подумать, что лямбда-выражения будут «сломаны». Чтобы избежать этого, python, похоже, смотрит вперед и понимает, что создаваемые постоянные функции ссылаются на локальные переменные в foo, которые скоро больше не будут доступны. Что он делает, так это создает «лексическое замыкание», которое сохраняет соответствующее локальное состояние, так что лямбда-выражения имеют доступ к переменной x
даже после завершения foo.
Вы можете видеть, что все лямбда-выражения ссылаются на одно и то же закрытие:
for f in funcs:
print(f.__closure__)
print(f.__code__.co_freevars)
(<cell at 0x10ebaa310: int object at 0x10e92bac0>,)
('x',)
(<cell at 0x10ebaa310: int object at 0x10e92bac0>,)
('x',)
(<cell at 0x10ebaa310: int object at 0x10e92bac0>,)
('x',)
, что заставляет меня поверить в то, что происходит какой-то упреждающий взгляд.
Предположительно это сделано для того, чтобы в дальнейшем можно было модифицировать функции. Мне не удалось определить, только ли это в примере 1, где у вас все еще есть доступ к переменной, или в целом, например, через какой-то атрибут dunder. Это обсуждается в ссылке «Изменить связанные переменные ....».
Если вы все же настаиваете на использовании лямбда-выражений, как указано выше, вы можете заменить
e.bind('<Double-Button-1>', lambda x: changeColor(x, e))
на
e.bind('<Double-Button-1>', lambda x, e=e: changeColor(x, e))
Здесь происходит то, что мы даем лямбда - значение по умолчанию (принудительное разыменование имени e
), которое затем приводит к ожидаемому более интуитивному результату.
Опять же, просто избегайте головной боли.