Подтвердить что ты не робот

Обнаружение ямбды Python

Я пытаюсь использовать замыкания, чтобы исключить переменную из сигнатуры функции (приложение должно записывать все функции, необходимые для подключения сигналов Qt для интерфейса, для управления большим количеством параметров в словаре, в котором хранятся значения),

Я не понимаю, почему случай использования lambda, не завернутого в другую функцию, возвращает имя для всех случаев.

names = ['a', 'b', 'c']

def test_fun(name, x):
    print(name, x)

def gen_clousure(name):
    return lambda x: test_fun(name, x)

funcs1 = [gen_clousure(n) for n in names]
funcs2 = [lambda x: test_fun(n, x) for n in names]

# this is what I want
In [88]: for f in funcs1:
   ....:     f(1)
a 1
b 1
c 1

# I do not understand why I get this
In [89]: for f in funcs2:
   ....:     f(1)
c 1
c 1
c 1
4b9b3361

Ответ 1

Причина в том, что замыкания (lambdas или иначе) закрываются по именам, а не значениям. Когда вы определяете lambda x: test_fun(n, x), n не оценивается, потому что он находится внутри функции. Он оценивается при вызове функции, когда это значение имеет последнее значение из цикла.

Вначале вы говорите, что хотите "использовать блокировки для исключения переменной из сигнатуры функции", но на самом деле это не так. (См. Ниже, однако, для способа, который может вас удовлетворить, в зависимости от того, что вы подразумеваете под "устранением".) Переменные внутри тела функции не будут вычисляться при определении функции. Чтобы заставить функцию принимать "моментальный снимок" переменной, как она существует во время определения функции, вы должны передать переменную в качестве аргумента. Обычный способ сделать это - дать функции аргумент, значением по умолчанию которого является переменная из внешней области. Посмотрите на разницу между этими двумя примерами:

>>> stuff = [lambda x: n+x for n in [1, 2, 3]]
>>> for f in stuff:
...     print f(1)
4
4
4
>>> stuff = [lambda x, n=n: n+x for n in [1, 2, 3]]
>>> for f in stuff:
...     print f(1)
2
3
4

Во втором примере передача n в качестве аргумента функции "блокирует" текущее значение n для этой функции. Вы должны сделать что-то подобное, если хотите заблокировать значение таким образом. (Если бы это не сработало, такие вещи, как глобальные переменные, вообще не работали, важно, чтобы во время использования были просмотрены свободные переменные.)

Обратите внимание, что ничего подобного не относится к lambdas. Те же правила определения области действия действуют, если вы используете def для определения функции, которая ссылается на переменные из охватывающей области.

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

>>> def makeFunc(n):
...     return lambda x: x+n
>>> stuff = [makeFunc(n) for n in [1, 2, 3]]
>>> for f in stuff:
...     print f(1)
2
3
4

Здесь внутренняя лямбда по-прежнему смотрит значение n, когда оно вызывается. Но n, на которое он ссылается, больше не является глобальной переменной, а локальной переменной внутри закрывающей функции makeFunc. Новое значение этой локальной переменной создается каждый раз, когда вызывается makeFunc, а возвращаемая лямбда создает замыкание, которое "сохраняет" значение локальной переменной, которое действовало для этого вызова makeFunc. Таким образом, каждая функция, созданная в цикле, имеет свою собственную переменную "private", называемую x. (Для этого простого случая это также можно сделать, используя лямбда для внешней функции --- stuff = [(lambda n: lambda x: x+n)(n) for n in [1, 2, 3]] ---, но это менее читаемо.)

Обратите внимание, что вам все равно нужно передать n в качестве аргумента, просто потому, что вы не передаете его в качестве аргумента той же функции, которая заканчивается в stuff список; вместо этого вы передаете его как аргумент вспомогательной функции, которая создает функцию, которую вы хотите поместить в stuff. Преимущество использования этого двухфункционального подхода заключается в том, что возвращаемая функция является "чистой" и не имеет дополнительного аргумента; это может быть полезно, если вы обертываете функции, которые принимают множество аргументов, и в этом случае может возникнуть путаница, чтобы вспомнить, где аргумент n был в списке. Недостатком является то, что, делая это таким образом, процесс создания функций усложняется, так как вам нужна другая закрывающая функция.

В результате получается компромисс: вы можете упростить процесс создания функции (т.е. не нужно использовать две вложенные функции), но тогда вы должны сделать результирующую функцию немного более сложной (т.е. она имеет это дополнительный аргумент n=n). Или вы можете сделать функцию проще (т.е. Не имеет аргумента n= n), но тогда вы должны сделать процесс создания функции более сложным (т.е. Вам нужно две вложенные функции для реализации механизма).