Почему результаты отображения карты() и списка различны?
Не удалось выполнить следующий тест:
#!/usr/bin/env python
def f(*args):
"""
>>> t = 1, -1
>>> f(*map(lambda i: lambda: i, t))
[1, -1]
>>> f(*(lambda: i for i in t)) # -> [-1, -1]
[1, -1]
>>> f(*[lambda: i for i in t]) # -> [-1, -1]
[1, -1]
"""
alist = [a() for a in args]
print(alist)
if __name__ == '__main__':
import doctest; doctest.testmod()
Другими словами:
>>> t = 1, -1
>>> args = []
>>> for i in t:
... args.append(lambda: i)
...
>>> map(lambda a: a(), args)
[-1, -1]
>>> args = []
>>> for i in t:
... args.append((lambda i: lambda: i)(i))
...
>>> map(lambda a: a(), args)
[1, -1]
>>> args = []
>>> for i in t:
... args.append(lambda i=i: i)
...
>>> map(lambda a: a(), args)
[1, -1]
Ответы
Ответ 1
Они различны, потому что значение i
как в выражении генератора, так и в списке comp вычисляется лениво, т.е. когда анонимные функции вызывают в f
.
К этому времени i
привязано к последнему значению, если t
, которое равно -1.
Итак, в основном, это то, что понимает список (аналогично для genexp):
x = []
i = 1 # 1. from t
x.append(lambda: i)
i = -1 # 2. from t
x.append(lambda: i)
Теперь лямбды несут вокруг замыкания, которое ссылается на i
, но i
привязано к -1 в обоих случаях, потому что это последнее значение, которому оно было присвоено.
Если вы хотите убедиться, что лямбда получает текущее значение i
, do
f(*[lambda u=i: u for i in t])
Таким образом, вы вынуждаете оценку i
во время создания замыкания.
Изменить. Существует одна разница между выражениями генератора и понятием списка: последние пропускают переменную цикла в окружающую область.
Ответ 2
Лямбда захватывает переменные, а не значения, поэтому код
lambda : i
всегда будет возвращать значение я в в настоящее время, связанное с закрытием. К моменту его вызова это значение установлено равным -1.
Чтобы получить то, что вы хотите, вам нужно будет зафиксировать фактическое привязку во время создания лямбда:
>>> f(*(lambda i=i: i for i in t)) # -> [-1, -1]
[1, -1]
>>> f(*[lambda i=i: i for i in t]) # -> [-1, -1]
[1, -1]
Ответ 3
Выражение f = lambda: i
эквивалентно:
def f():
return i
Выражение g = lambda i=i: i
эквивалентно:
def g(i=i):
return i
i
является свободной переменной в первом случае и привязана к функциональному параметру во втором случае, т.е. локальная переменная в этом случае. Значения параметров по умолчанию оцениваются во время определения функции.
Выражение генератора является ближайшей охватывающей областью (где i
определено) для i
name в выражении lambda
, поэтому i
разрешается в этом блоке:
f(*(lambda: i for i in (1, -1)) # -> [-1, -1]
i
является локальной переменной блока lambda i: ...
, поэтому объект, к которому он относится, определен в этом блоке:
f(*map(lambda i: lambda: i, (1,-1))) # -> [1, -1]