Что (лямбда) функционируют получение закрытий?

Недавно я начал играть вокруг с 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 изменяет его значение?
237
задан martineau 14 April 2017 в 15:54
поделиться

4 ответа

На ваш второй вопрос был дан ответ, но что касается вашего первого:

что именно фиксирует замыкание?

Область видимости в Python является динамической и ] лексический. Замыкание всегда будет помнить имя и область действия переменной, а не объект, на который она указывает. Поскольку все функции в вашем примере созданы в одной области и используют одно и то же имя переменной, они всегда ссылаются на одну и ту же переменную.

РЕДАКТИРОВАТЬ: Что касается вашего другого вопроса о том, как преодолеть это, на ум приходят два способа:

  1. Самый краткий, но не строго эквивалентный способ - , рекомендованный Адрианом Плиссоном . Создайте лямбду с дополнительным аргументом и установите значение по умолчанию для дополнительного аргумента для объекта, который вы хотите сохранить.

  2. Немного более подробным, но менее хитрым было бы создание новой области каждый раз, когда вы создаете лямбда:

     >>> 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)] 
     
150
ответ дан 23 November 2019 в 03:24
поделиться

Отвечая на ваш второй вопрос, самым элегантным способом сделать это было бы использование функции, принимающей два параметра вместо массива:

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)
2
ответ дан 23 November 2019 в 03:24
поделиться

Рассмотрим следующий код:

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
21
ответ дан 23 November 2019 в 03:24
поделиться

вы можете принудительно захватить переменную, используя аргумент со значением по умолчанию:

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

185
ответ дан 23 November 2019 в 03:24
поделиться
Другие вопросы по тегам:

Похожие вопросы: