#python
Помогите, пожалуйста, разобраться в закулисьях этого кода: def someFunction(): list = [] for i in range(5): list.append(lambda x: i ** x) return list #вызов функции list = someFunction() print(list[1](2)) По идее, результатом этого кода должна выступать единица, но на самом деле выводится 16. Я не совсем понимаю, как работает эта программа. По-моему: Компилятор начинает свою работу с 1-ой строки. Видит def и пропускает всю функцию. Затем добирается до строки list = someFunction() после которой начинает интерпретировать функцию someFunction. Добирается до цикла for, и начинается первая итерация. Но команда, добавляющая элемент в конец списка, в качестве аргумента принимает функцию, которая, в свою очередь, еще не получила в распоряжение свой аргумент. Что добавилось в список? Я предполагаю, что создался объект функции, который связался с конкретным элементом списка. Так, я думаю, происходит на всех итерациях. После чего функция возвращает список, в котором находятся ссылки на созданные объекты функций. Если всё так и есть на самом деле, то это логично. Но что происходит при чтении элемента из списка? Окей. Если выплывает 16 вместо 1, скорее всего, все объекты функций в качестве итерационной переменной принимают 4-ку, т.е. последнюю итерацию. И здесь не совсем понятно, почему при создании объекта функции не сохранилось значение счетчика цикла? Предполагаю, что вы можете мне сказать: "А как, по-твоему, это значение сохранится, если его нет в параметрах lambda-функции?". Но тогда как она находит и подставляет последнюю итерацию? Да и вообще, пока писал этот пост еще больше запутался: с чего бы это компилятор создает объект-функции до того, как эта функция вызывается? Ведь объект функции someFunction конструировался тогда, когда мы вызвали эту функцию. А объекты lambda-функции создались ещё до их вызова. Будьте добры, помогите разобраться в данном вопросе. Спасибо.
Ответы
Ответ 1
Переменная в вашем примере всегда будет равна 4 потому, что поиск переменной в объемлющей области видимости (по правилу LEGB) происходит в момент вызова вложенной функции, а не при создании объекта. Т.е. внутри лямбда-функции i - это "ссылка на i в объемлющей области", а не "ссылка на int(0)" или "ссылка на int(4)". Пример из ответа @aleks.andr с аргументом по умолчанию сработает потому, что вычисление значения по умолчанию происходит один раз в момент создания вложенной функции (что, кстати, тоже способно приводить к интересным эффектам, если создавать mutable-объект и изменять его внутри функции: def f(a=[]): a.append(0) ; print(a)) P.S. Не перекрывайте builtin list в своем коде, это может привести к удивительным последствиям.Ответ 2
И здесь не совсем понятно, почему при создании объекта функции не сохранилось значение счетчика цикла - она же ссылается на переменную i, которая после всех итераций осталась равна 4. Вам действительно нужно захватить переменную i при объявлении лямбды. Вот рабочий код, возвращающий вашу единицу: def someFunction(): list = [] for i in range(5): list.append(lambda x,i=i: i ** x) return list #вызов функции list = someFunction() print(list[1](2))
Комментариев нет:
Отправить комментарий