Почему результаты map () и понимания списка различны? - PullRequest
11 голосов
/ 26 сентября 2008

Следующий тест не пройден:

#!/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]

Ответы [ 3 ]

9 голосов
/ 26 сентября 2008

Они различаются, потому что значение 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, выполните

f(*[lambda u=i: u for i in t])

Таким образом, вы форсируете оценку i во время создания закрытия.

Редактировать : Существует одно различие между выражениями генератора и списками: последние пропускают переменную цикла в окружающую область.

5 голосов
/ 26 сентября 2008

Лямбда фиксирует переменные, а не значения, поэтому код

lambda : i

всегда будет возвращать значение 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]
4 голосов
/ 26 сентября 2008

Выражение f = lambda: i эквивалентно:

def f():
    return i

Выражение g = lambda i=i: i эквивалентно:

def g(i=i):
    return i

i - это свободная переменная в первом случае, и она связана с параметром функции во втором случае, то есть в этом случае это локальная переменная. Значения параметров по умолчанию оцениваются во время определения функции.

Выражение генератора - это ближайшая охватывающая область (где определено i) для i имени в выражении 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]
...