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

Как создать список 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. Как изменились существующие лямбда-объекты между этими двумя вызовами печати?

Связанный вопрос: Почему результаты отображения карты() и списка различны?

4b9b3361

Ответ 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) ]