2012-02-09 2 views
14

Прочитайте вопрос о переполнении стека некоторое время назад со следующим синтаксисомпутаться с лямбда и список понимания

In [1]: [lambda: x for x in range(5)][0]() 
Out[1]: 4 
In [2]: [lambda: x for x in range(5)][2]() 
Out[2]: 4 

Но я имею трудное время, чтобы понять, почему именно выход этого приходит как 4, мой понимание всегда дает последнее значение списка в качестве выходного,

In [4]: [lambda: x for x in [1,5,7,3]][0]() 
Out[4]: 3 

, но до сих пор не уверен, как это синтаксис заканчивается с последним значением.

Был бы очень рад, если я могу получить надлежащее объяснение этого синтаксиса

+0

Как вы думаете, вы могли бы найти этот вопрос, который вы упомянули? Это интересно, я тоже хотел бы это выяснить :) – kevintodisco

+2

'В [84]: [z() для z in (lambda: x для x в диапазоне (5))]' 'Out [84]: [0, 1, 2, 3, 4] ' ' В [85]: [z() для z в [lambda: x для x в диапазоне (5)]] ' ' Out [85]: [4 , 4, 4, 4, 4] ' если генератор используется вместо понимания списка, то и выход будет отличаться! – shahjapan

+1

@ktodisco: Я уверен, что это мой вопрос: http://stackoverflow.com/questions/9189702/python-closures-and-cells-closed-over-values ​​ –

ответ

15

Это не относится ни к каким перечням, ни к лямбдам. Речь идет о правилах определения области видимости в Python. Давайте перепишем список понимания в эквивалентный цикл:

funcs = [] 
for x in range(5): 
    def f(): return x 
    funcs.append(f) 
funcs[0]() # returns 4 

Здесь мы можем видеть, что мы последовательно строить функции и сохранять их в списке. Когда вызывается одна из этих функций, все, что происходит, - это значение x, которое просматривается и возвращается. Но x - это переменная, значение которой изменяется, поэтому конечное значение 4 - это то, что всегда возвращается. Можно даже изменить значение x после цикла, например,

x = 32 
funcs[2]() # returns 32 

Чтобы получить поведение, вы ожидали, Python должен был бы объем в for содержимое в виде блока; это не так. Обычно это не проблема, и ее достаточно легко обойти.

4

Для ЛЦ с п итераций, x присваивается элементы от 0 до п-1 исходной последовательности. На последней итерации назначен x. Следует отметить, что всегда один и тот же x, и вызов функции в конце возвращает все оставшиеся x.

+2

можете ли вы разбить код на несколько шагов, чтобы я мог лучше понять его :) – avasal

+0

@avasal Лямбда включает * имя * 'x', а не значение' x'. Когда LC завершается, у вас есть список lambdas, возвращающий значение, связанное с именем 'x' - самым последним значением. – Daenyth

+0

Если LC завершает первое и последнее значение возвращается лямбда, я смущен, почему есть 5 лямбда-функций В [6]: [lambda: x для x в диапазоне (5)] Out [6]: [<функция в 0x16b7ed8>, <функция в 0x16b7e60>, <функция в 0x16c10c8>, <функция в 0x16c1140>, <функция в 0x16c11b8>] – avasal

1

Это исправить:

[(lambda(i): lambda: i)(x) for x in range(5)][2]() 

Проблема заключается в том, что вы не захватывая значение х на каждой итерации списка понимания, вы захватив переменную каждый раз через ,

+1

Мне не нужно исправлять это, мне нужно понять, что там происходит :) – avasal

+1

Я думаю, что вопрос OP состоит в том, чтобы понять, как интерпретирует python, когда он не фиксирует значение 'x'. :) – shahjapan

3

Позвольте мне сломать код для моего понимания

In [39]: [lambda: x for x in [1,5,7,3]] 
Out[39]: 
[<function <lambda> at 0x2cd1320>, 
<function <lambda> at 0x2cd12a8>, 
<function <lambda> at 0x2cd10c8>, 
<function <lambda> at 0x2cd1050>] 

выше дает список функций

In [40]: [lambda: x for x in [1,5,7,3]][1] 
Out[40]: <function <lambda> at 0x2cd1488> 

Индекс 1 дает 1 функцию из списка функций.

Теперь эта функция будет применяться к x, которая всегда имеет последнее значение списка. То y всегда дает последнее значение в качестве результата.

как показано ниже.

In [41]: [lambda: 2][0]() 
Out[41]: 2 


In [42]: alist = [1,5,7,3,4,5,6,7] 

x для x в [1,5,7,3] эквивалентно функции f (x) ниже. и
лямбда: х для х в [1,5,7,3] эквивалентно лямбда: 3

In [43]: def f(x): 
    ....:  for x in alist: 
    ....:   pass 
    ....:  return x 
In [44]: print f(alist) 
7 
0

Как уже говорили другие, вы столкнулись с общей проблемой петли замыкания; то есть проблема, заключающаяся в том, что замыкание фиксирует переменные по ссылке. В этом случае вы захватываете переменную, значение которой изменяется со временем (как и со всеми переменными цикла), поэтому при запуске она имеет другое значение, чем при ее создании.

Чтобы выполнить то, что вы хотите, вам нужно зафиксировать значение переменной во время создания лямбда. sblom предложил решение обернув его в непосредственно-выполняемой функции:

[(lambda(i): lambda: i)(x) for x in range(5)][2]() 

Другое решение, что вы будете часто видеть в Python использует аргументы по умолчанию, который будет также оценить значение во время создания:

[lambda i=x: i for x in range(5)][2]() 

(обычно это написано lambda x=x: x, но для ясности я переименовал переменную)

Смежные вопросы