Как создать список Python lambdas (в понимании списка/для цикла)?
Я хочу создать список лямбда-объектов из списка констант в 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, но затем использование более раннего оператора печати:
for f in listOfLambdas:
print f(),
все равно дает мне все 25
s. Как изменились существующие лямбда-объекты между этими двумя вызовами печати?
Связанный вопрос: Почему результаты отображения карты() и списка различны?
Ответы
Ответ 1
Я предполагаю, что лямбда, которую вы создаете в понимании списка, привязана к переменной 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
Другой способ думать об этом - это то, что лямбда "захватывает" свою лексическую среду в том месте, где она создана. Итак, если вы дадите ему num, он фактически не разрешит это значение до его вызова. Это и запутывающее, и сильное.
Ответ 2
У вас есть:
listOfLambdas = [lambda: i*i for i in range(6)]
for f in listOfLambdas:
print f()
Вывод:
25
25
25
25
25
25
Вам нужно карри! Помимо того, что вы восхититесь, используйте это значение по умолчанию "взломать".
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
. То, где происходит волшебство.
Ответ 3
Когда выполняются операторы функций, они привязаны к их (лексически) охватывающей области.
В вашем фрагменте lambdas привязаны к глобальному охвату, потому что for
suite не выполняются как независимая область в Python. В конце цикла for
объект num
привязан в охватывающей области. Демо-ролик:
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, с нашими новообретенными знаниями, мы можем определить, что создаваемые lambdas ссылаются на (одиночный) 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 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
about 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>,)
Ответ 4
listOfLambdas = [lambda i=i: square(i) for i in listOfNumbers]
или
listOfLambdas = map(lambda i: lambda: square(i), listOfNumbers)
Ответ 5
Попробуйте использовать() вместо []:
listOfLambdas = (lambda: square(i) for i in listOfNumbers)
И вы получите:
1
4
9
16
25
Ответ 6
Иногда я обнаруживаю, что определение фактических классов для объектов функций упрощает понимание того, что происходит:
>>> 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
>>>
Конечно, это немного более многословно, чем использование лямбда или закрытия, но я считаю, что это легче понять, когда я пытаюсь делать неочевидные вещи с функциями.
Ответ 7
Вы также можете сделать:
>>> 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]
Ответ 8
В качестве дополнительного комментария я хотел бы изложить возможность генерации списков лямбда-функций из симплексных матриц (я не знаю, является ли это лучшим способом сделать это, но так я это и считаю удобно):
import sympy as sp
sp.var('Ksi')
# generate sympy expressions for Berstein 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) ]