Страницы

Поиск по вопросам

суббота, 14 декабря 2019 г.

Как происходит создание объекта функции?

#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))

Комментариев нет:

Отправить комментарий