Недавно я начал играть вокруг с Python, и я пришел что-то специфическое в способе, которым работают закрытия. Рассмотрите следующий код:
adders=[0,1,2,3]
for i in [0,1,2,3]:
adders[i]=lambda a: i+a
print adders[1](3)
Это создает простой массив из функций, которые берут единственный вход и возврат, которые вводят добавленный числом. Функции создаются в for
цикл, где итератор i
выполнения от 0
кому: 3
. Для каждого из этих чисел a lambda
функция создается, который получает i
и добавляет его к входу функции. Последняя строка называет второе lambda
функция с 3
в качестве параметра. К моему удивлению вывод был 6
.
Я ожидал a 4
. Мое обоснование было: в Python все - объект, и таким образом каждая переменная важна указатель на него. При создании lambda
закрытия для i
, Я ожидал, что это сохранит указатель на целочисленный объект, которым в настоящее время указывают i
. Это означает это когда i
присвоенный новый целочисленный объект это не должно производить ранее созданные закрытия. К сожалению, осмотр adders
массив в отладчике показывает, что делает. Все lambda
функции относятся к последнему значению i
, 3
, который приводит к adders[1](3)
возврат 6
.
Которые заставляют меня задаться вопросом о следующем:
lambda
функции для получения текущего значения i
способом это не будет затронуто когда i
изменяет его значение?На ваш второй вопрос был дан ответ, но что касается вашего первого:
что именно фиксирует замыкание?
Область видимости в Python является динамической и ] лексический. Замыкание всегда будет помнить имя и область действия переменной, а не объект, на который она указывает. Поскольку все функции в вашем примере созданы в одной области и используют одно и то же имя переменной, они всегда ссылаются на одну и ту же переменную.
РЕДАКТИРОВАТЬ: Что касается вашего другого вопроса о том, как преодолеть это, на ум приходят два способа:
Самый краткий, но не строго эквивалентный способ - , рекомендованный Адрианом Плиссоном . Создайте лямбду с дополнительным аргументом и установите значение по умолчанию для дополнительного аргумента для объекта, который вы хотите сохранить.
Немного более подробным, но менее хитрым было бы создание новой области каждый раз, когда вы создаете лямбда:
>>> adders = [0,1,2,3]
>>> for i in [0,1,2,3]:
... adders [i] = (lambda b: lambda a: b + a) (i)
... {{1 }} >>> adders [1] (3)
4
>>> adders [2] (3)
5
Объем здесь создается с использованием новой функции (лямбда, для краткости), которая связывает свой аргумент и передает значение, которое вы хотите привязать в качестве аргумента. Однако в реальном коде у вас, скорее всего, будет обычная функция вместо лямбда для создания новой области:
def createAdder (x):
return lambda y: y + x
adders = [createAdder (i) for i in range (4)]
Отвечая на ваш второй вопрос, самым элегантным способом сделать это было бы использование функции, принимающей два параметра вместо массива:
add = lambda a, b: a + b
add(1, 3)
Однако использование лямбды здесь немного глупо. Python предоставляет нам модуль operator
, который обеспечивает функциональный интерфейс для основных операторов. Приведенная выше лямбда имеет ненужные накладные расходы только для вызова оператора сложения:
from operator import add
add(1, 3)
Я понимаю, что вы играете, пытаясь изучить язык, но я не могу представить ситуацию, в которой я бы использовал массив функций, где странности Python в области скопирования могли бы помешать.
Если вы хотите, вы можете написать небольшой класс, который использует ваш синтаксис индексации массивов:
class Adders(object):
def __getitem__(self, item):
return lambda a: a + item
adders = Adders()
adders[1](3)
Рассмотрим следующий код:
x = "foo"
def print_x():
print x
x = "bar"
print_x() # Outputs "bar"
Я думаю, что большинство людей не сочтут это путаницей. Это ожидаемое поведение.
Так почему же люди думают, что это будет по-другому, когда это делается в цикле? Я знаю, что сам совершил эту ошибку, но я не знаю почему. Дело в цикле? Или, может быть, лямбда?
В конце концов, цикл - это просто более короткая версия:
adders= [0,1,2,3]
i = 0
adders[i] = lambda a: i+a
i = 1
adders[i] = lambda a: i+a
i = 2
adders[i] = lambda a: i+a
i = 3
adders[i] = lambda a: i+a
вы можете принудительно захватить переменную, используя аргумент со значением по умолчанию:
>>> for i in [0,1,2,3]:
... adders[i]=lambda a,i=i: i+a # note the dummy parameter with a default value
...
>>> print( adders[1](3) )
4
идея состоит в том, чтобы объявить параметр (с умным названием i
) и присвойте ему значение по умолчанию для переменной, которую вы хотите захватить (значение i
)