Ловушка замыканий: Почему ваши лямбды в цикле сломаны (Late Binding)
Вы пишете код, который генерирует список функций (например, колбэки для кнопок в UI или динамические фильтры). Кажется, что все логично, но на выходе получаете сюрприз.
Проблемный код:
Почему так происходит?
Это называется Late Binding (позднее связывание).
В Python замыкания (closures) захватывают переменные по ссылке, а не по значению.
Когда вы объявляете
К моменту, когда вы начинаете вызывать функции из списка
Как лечить?
Есть два каноничных способа заставить Python запомнить значение «здесь и сейчас».
1. Аргумент по умолчанию (Hack way)
Значения аргументов по умолчанию вычисляются в момент определения функции.
Это работает быстро, но выглядит немного грязно и может сбить с толку линтеры или коллег.
2.
Более чистый и явный способ.
Где это стреляет в реальной жизни?
- Генерация
- Динамическое создание
- Патчинг тестов в циклах.
Не дайте переменным пережить свое время.
#python #internals #functionalprogramming #gotchas
📲 Мы в MAX
👉@BookPython
Вы пишете код, который генерирует список функций (например, колбэки для кнопок в UI или динамические фильтры). Кажется, что все логично, но на выходе получаете сюрприз.
Проблемный код:
# Хотим создать 3 функции, которые возвращают 0, 1 и 2 соответственно
funcs = []
for i in range(3):
funcs.append(lambda: i)
# Проверяем
results = [f() for f in funcs]
print(results)
# Ожидание: [0, 1, 2]
# Реальность: [2, 2, 2]
Почему так происходит?
Это называется Late Binding (позднее связывание).
В Python замыкания (closures) захватывают переменные по ссылке, а не по значению.
Когда вы объявляете
lambda: i, Python не сохраняет текущее число 0, 1 или 2. Он сохраняет инструкцию: «когда меня вызовут, пойди в локальную область видимости, найди переменную с именем i и возьми ее значение».К моменту, когда вы начинаете вызывать функции из списка
results, цикл for уже завершился. Переменная i в этой области видимости навсегда осталась равной 2. Все три лямбды смотрят на одну и ту же переменную i.Как лечить?
Есть два каноничных способа заставить Python запомнить значение «здесь и сейчас».
1. Аргумент по умолчанию (Hack way)
Значения аргументов по умолчанию вычисляются в момент определения функции.
funcs = []
for i in range(3):
# i=i создает локальную переменную i внутри функции
# и присваивает ей текущее значение i из цикла
funcs.append(lambda i=i: i)
Это работает быстро, но выглядит немного грязно и может сбить с толку линтеры или коллег.
2.
functools.partial (Enterprise way)Более чистый и явный способ.
partial создает новый callable-объект, «замораживая» переданные аргументы.
from functools import partial
funcs = []
for i in range(3):
# Здесь значение i фиксируется жестко
funcs.append(partial(lambda x: x, i))
Где это стреляет в реальной жизни?
- Генерация
command для кнопок в Tkinter/PyQt.- Динамическое создание
task в asyncio циклах.- Патчинг тестов в циклах.
Не дайте переменным пережить свое время.
#python #internals #functionalprogramming #gotchas
👉@BookPython
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5❤2