Я хочу создать список лямбда-объектов из списка констант в Python; например:
listOfNumbers = [1,2,3,4,5]
square = lambda x: x * x
listOfLambdas = [lambda: square(i) for i in listOfNumbers]
Однако это создаст список лямбда-объектов, когда я их запустил:
for f in listOfLambdas:
print f(),
Я ожидал, что он напечатает
1 4 9 16 25
Вместо этого он печатает:
25 25 25 25 25
Кажется, что всем лямбдам был задан неправильный параметр. Я сделал что-то не так, и есть ли способ исправить? Думаю, я использую Python 2.4.
Обновлено: еще немного пробовать, и вот что:
listOfLambdas = []
for num in listOfNumbers:
action = lambda: square(num)
listOfLambdas.append(action)
print action()
Печатает ожидаемые квадраты от 1 до 25, но затем с использованием предыдущего оператора print:
for f in listOfLambdas:
print f(),
все еще дает мне все 25. Как изменились существующие лямбда-объекты между этими двумя вызовами печати?
Связанный вопрос: Почему результаты отображения map () и списка различаются?
Отвечает ли это на ваш вопрос? Что фиксируют замыкания (лямбда) функций?






Я предполагаю, что лямбда, которую вы создаете в понимании списка, привязана к переменной i, которая в конечном итоге заканчивается на 5. Таким образом, когда вы оцениваете лямбды постфактум, все они привязаны к 5 и в конечном итоге вычисляют 25. То же самое происходит с num во втором примере. Когда вы оцениваете лямбду внутри цикла, оно num не изменилось, поэтому вы получаете правильное значение. После цикла число 5 ...
Я не совсем уверен, что вы собираетесь делать, поэтому не знаю, как предложить решение. Как насчет этого?
def square(x): return lambda : x*x
listOfLambdas = [square(i) for i in [1,2,3,4,5]]
for f in listOfLambdas: print f()
Это дает мне ожидаемый результат:
1
4
9
16
25
Другой способ думать об этом состоит в том, что лямбда «захватывает» свое лексическое окружение в точке, где она создается. Итак, если вы дадите ему число, он фактически не разрешит это значение до тех пор, пока не будет вызван. Это одновременно сбивает с толку и сильно.
Иногда я обнаруживаю, что определение реальных классов для функциональных объектов упрощает понимание того, что происходит:
>>> class square(object):
... def __init__(self, val):
... self.val = val
... def __call__(self):
... return self.val * self.val
...
>>> l = [1,2,3,4,5]
>>> funcs = [square(i) for i in l]
>>> for f in funcs:
... print f()
...
1
4
9
16
25
>>>
Конечно, это немного более подробно, чем использование лямбда-выражений или замыканий, но мне легче понять это, когда я пытаюсь делать неочевидные вещи с помощью функций.
У тебя есть:
listOfLambdas = [lambda: i*i for i in range(6)]
for f in listOfLambdas:
print f()
Выход:
25
25
25
25
25
25
Вам нужно карри! Помимо того, что это вкусно, используйте значение по умолчанию "hack".
listOfLambdas = [lambda i=i: i*i for i in range(6)]
for f in listOfLambdas:
print f()
Выход:
0
1
4
9
16
25
Обратите внимание на i=i. Вот где происходит волшебство.
Прохладный. Этот "взлом" где-нибудь задокументирован? Есть ли способ лучше карри? Кроме того, пожалуйста, никогда больше не упоминайте свою вращающуюся кровать.
Я никогда не видел, чтобы это прямо упоминалось в какой-либо документации. Он использует тот факт, что значения по умолчанию для параметров функции назначаются во время создания функции, что задокументировано.
Этот пример почти идентичен Почему лямбда-выражения, определенные в цикле с разными значениями, возвращают один и тот же результат? из документации.
Хорошая находка. Я не могу точно вспомнить (не могу поверить, что использую SO 5 лет!), Но думаю, что я задумал этот пример самостоятельно. Полагаю, есть всего несколько способов объяснить это явление на примере.
listOfLambdas = [lambda i=i: square(i) for i in listOfNumbers]
Или же
listOfLambdas = map(lambda i: lambda: square(i), listOfNumbers)
Когда операторы функции выполняются, они привязаны к своей (лексической) охватывающей области.
В вашем фрагменте лямбды привязаны к глобальной области видимости, потому что наборы for не выполняются как модуль с независимой областью видимости в Python. В конце цикла fornum привязан к охватывающей области. Демо:
for num in range(1, 6):
pass
assert num == 5 # num is now bound in the enclosing scope
Поэтому, когда вы привязываете идентификаторы в цикле for, вы фактически манипулируете охватывающей областью.
for num in range(1, 6):
spam = 12
assert num == 5 # num is now bound in the enclosing scope
assert spam == 12 # spam is also bound in the enclosing scope
То же самое для понимания списков:
[num for num in range(1, 6)]
assert num == 5
Я знаю, что это потрясающе. Anywho, с нашими новообретенными знаниями, мы можем определить, что лямбда-выражения, которые вы создаете, относятся к (единственному) идентификатору num, привязанному во включающей области. В этом должно быть больше смысла:
functions = []
for number in range(1, 6):
def fun():
return number
functions.append(fun)
assert all(fun() == 5 for fun in functions)
assert all(fun() is number for fun in functions)
И вот самая крутая часть, которая еще больше это демонстрирует:
# Same as above -- commented out for emphasis.
#functions = []
#for number in range(1, 6):
# def fun():
# return number
# functions.append(fun)
#assert all(fun() == 5 for fun in functions)
#assert all(fun() is number for fun in functions)
number = 6 # Rebind 6 in the scope and see how it affects the results.
assert all(fun() == 6 for fun in functions)
Таким образом, решение этой проблемы, конечно же, состоит в том, чтобы создать новую охватывающую область для каждого number, который вы хотите привязать. В Python вы можете создавать новые охватывающие области с модулями, классами и функциями. Обычно функцию используют только для создания новой охватывающей области для другой функции.
В Python закрытие - это функция, которая возвращает другую функцию. Вроде как конструктор функции. Посмотрите на get_fun в следующем примере:
def get_fun(value):
""":return: A function that returns :param:`value`."""
def fun(): # Bound to get_fun's scope
return value
return fun
functions = []
for number in range(1, 6):
functions.append(get_fun(number))
assert [fun() for fun in functions] == range(1, 6)
Поскольку get_fun - это функция, у нее должна быть собственная внутренняя область видимости. Каждый раз, когда вы вызываете get_fun со значением, создается небольшая таблица для отслеживания привязок внутри нее; то есть он говорит: «В этой области идентификатор value указывает на то, что было передано». Эта область исчезает в конце выполнения функции, если нет причин для ее сохранения.
Если вы возвращаете функцию из области видимости, это хороший повод для того, чтобы части «таблицы области видимости» оставались без внимания - возвращаемая вами функция могла ссылаться на вещи из этой таблицы области видимости, когда вы вызываете ее позже. По этой причине, когда fun создается в get_fun, Python сообщает fun о таблице области действия get_fun, которую fun держит под рукой, когда это необходимо.
Вы можете прочитать больше о деталях и технической терминологии (которую я немного смягчил) в Документы Python по модели выполнения. Вы также можете посмотреть на части охватывающей области, на которые функция ссылается с помощью print fun.__closure__. Выше мы видим ссылку на value, которая имеет тип int:
# Same as before, commented out for emphasis.
#functions = []
#for number in range(1, 6):
# functions.append(get_fun(number))
#assert [fun() for fun in functions] == range(1, 6)
print functions[0].__closure__
# Produces: (<cell at 0x8dc30: int object at 0x1004188>,)
Попробуйте использовать () вместо []:
listOfLambdas = (lambda: square(i) for i in listOfNumbers)
И вы получите:
1
4
9
16
25
Вы также можете:
>>> def squares():
... for i in [1,2,3,4,5]:
... yield lambda:i*i
...
>>> print [square() for square in squares()]
[1, 4, 9, 16, 25]
В качестве дополнительного комментария я хотел бы выделить возможность генерации списков лямбда-функций из симпозиумных матриц (я не знаю, лучший ли это способ сделать это, но я так делаю и считаю это удобным):
import sympy as sp
sp.var('Ksi')
# generate sympy expressions for Berstein's polynomials
B_expr = sp.Matrix([sp.binomial(3, i) * Ksi**i * (1 - Ksi)**(3-i) for i in range(4)])
# lambdify them
B = [sp.lambdify((Ksi), B_expr[i]) for i in range(4) ]
Я думаю, что стоит "продвигать" комментарий @abarnert ниже: Это в основном FAQ в официальной документации.