20

У меня есть список и функцию lambda определяется какНеожиданное Выход из списка (генератора)

In [1]: i = lambda x: a[x] 
In [2]: alist = [(1, 2), (3, 4)] 

Затем я попробовать два различных метода для расчета простую сумму

Первый метод.

In [3]: [i(0) + i(1) for a in alist] 
Out[3]: [3, 7] 

Второй способ.

In [4]: list(i(0) + i(1) for a in alist) 
Out[4]: [7, 7] 

Оба результата неожиданно отличаются друг от друга. Почему это происходит?

+0

он показывает ошибку для меня. –

+0

@AvinashRaj Запуск второго метода сначала дает 'NameError: глобальное имя 'a' не определен' –

+1

Там лежит ваша проблема, a определяется в первом как (3, 4), тогда функция list() всегда принимает это 'a' – TheGeorgeous

ответ

15

Это поведение было исправлено в python 3. Когда вы используете понимание списка [i(0) + i(1) for a in alist], вы определяете a в своей области, доступной для i. В новой сессии list(i(0) + i(1) for a in alist) будет вызывать ошибку.

>>> i = lambda x: a[x] 
>>> alist = [(1, 2), (3, 4)] 
>>> list(i(0) + i(1) for a in alist) 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "<stdin>", line 1, in <genexpr> 
    File "<stdin>", line 1, in <lambda> 
NameError: global name 'a' is not defined 

список понимание не является генератором: Generator expressions and list comprehensions.

Generator expressions are surrounded by parentheses (“()”) and list comprehensions are surrounded by square brackets (“[]”).

В вашем примере list() как класс имеет свою собственную сферу переменных и имеет доступ к глобальным переменным в большинстве. Когда вы используете это, i будет искать a внутри этой области. Попробуйте это в новой сессии:

>>> i = lambda x: a[x] 
>>> alist = [(1, 2), (3, 4)] 
>>> [i(0) + i(1) for a in alist] 
[3, 7] 
>>> a 
(3, 4) 

Сравните это с этим в другой сессии:

>>> i = lambda x: a[x] 
>>> alist = [(1, 2), (3, 4)] 
>>> l = (i(0) + i(1) for a in alist) 
<generator object <genexpr> at 0x10e60db90> 
>>> a 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
NameError: name 'a' is not defined 
>>> [x for x in l] 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "<stdin>", line 1, in <genexpr> 
    File "<stdin>", line 1, in <lambda> 
NameError: global name 'a' is not defined 

При запуске list(i(0) + i(1) for a in alist) вы пройдете генератор (i(0) + i(1) for a in alist) к list класса, который будет пытаться преобразовать его в список в своей области перед возвратом списка. Для этого генератора, который не имеет доступа к лямбда-функции, переменная a не имеет никакого значения.

Объект-генератор <generator object <genexpr> at 0x10e60db90> потерял имя переменной a. Затем, когда list пытается вызвать генератор, функция лямбда вызовет ошибку для undefined a.

Поведение списковых в отличие от генераторов также упоминается here:

List comprehensions also "leak" their loop variable into the surrounding scope. This will also change in Python 3.0, so that the semantic definition of a list comprehension in Python 3.0 will be equivalent to list(). Python 2.4 and beyond should issue a deprecation warning if a list comprehension's loop variable has the same name as a variable used in the immediately surrounding scope.

В Python3:

>>> i = lambda x: a[x] 
>>> alist = [(1, 2), (3, 4)] 
>>> [i(0) + i(1) for a in alist] 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "<stdin>", line 1, in <listcomp> 
    File "<stdin>", line 1, in <lambda> 
NameError: name 'a' is not defined 
+0

Как он производит вывод для обоих? –

+0

@AvinashRaj: сначала запустив понимание списка, 'a' все еще привязан к' (3, 4) 'кортежу. –

1

a в глобальном масштабе. Так он должен дать ошибку

Решение:

i = lambda a, x: a[x]

5

Вы должны сделать a параметр к вашей функции лямбда.Это работает, как ожидалось:

In [10]: alist = [(1, 2), (3, 4)] 

In [11]: i = lambda a, x: a[x] 

In [12]: [i(a, 0) + i(a, 1) for a in alist] 
Out[12]: [3, 7] 

In [13]: list(i(a, 0) + i(a, 1) for a in alist) 
Out[13]: [3, 7] 

Альтернативный способ получить тот же результат будет:

In [14]: [sum(a) for a in alist] 
Out[14]: [3, 7] 

EDIT этот ответ только простой обходной путь и не является реальным ответом на этот вопрос. Наблюдаемый эффект немного сложнее, см. Мой other answer.

1

После того, как [i(0) + i(1) for a in alist] выполнен, a становится (3,4).

Тогда, когда ниже линия выполняется:

list(i(0) + i(1) for a in alist) 

(3,4) значения используется как время с помощью функции лямбды i в качестве значения a, поэтому она печатает [7,7].

Вместо этого вы должны определить вашу лямбду функции, имеющие два параметра: a и x.

i = lambda a,x : a[x] 
5

Важные вещи, чтобы понять здесь

  1. выражения генератор будет создавать объекты внутри функции, но список понимание не будет.

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

Позволяет увидеть байтовые коды выражения генератора

>>> dis(compile('(i(0) + i(1) for a in alist)', 'string', 'exec')) 
    1   0 LOAD_CONST    0 (<code object <genexpr> at ...>) 
       3 MAKE_FUNCTION   0 
       6 LOAD_NAME    0 (alist) 
       9 GET_ITER    
      10 CALL_FUNCTION   1 
      13 POP_TOP    
      14 LOAD_CONST    1 (None) 
      17 RETURN_VALUE   

Он загружает код объекта, а затем это делает его функцией. Позволяет увидеть фактический объект кода.

>>> dis(compile('(i(0) + i(1) for a in alist)', 'string', 'exec').co_consts[0]) 
    1   0 LOAD_FAST    0 (.0) 
     >> 3 FOR_ITER    27 (to 33) 
       6 STORE_FAST    1 (a) 
       9 LOAD_GLOBAL    0 (i) 
      12 LOAD_CONST    0 (0) 
      15 CALL_FUNCTION   1 
      18 LOAD_GLOBAL    0 (i) 
      21 LOAD_CONST    1 (1) 
      24 CALL_FUNCTION   1 
      27 BINARY_ADD   
      28 YIELD_VALUE   
      29 POP_TOP    
      30 JUMP_ABSOLUTE   3 
     >> 33 LOAD_CONST    2 (None) 
      36 RETURN_VALUE   

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

Но в случае списка понимания,

>>> dis(compile('[i(0) + i(1) for a in alist]', 'string', 'exec')) 
    1   0 BUILD_LIST    0 
       3 LOAD_NAME    0 (alist) 
       6 GET_ITER    
     >> 7 FOR_ITER    28 (to 38) 
      10 STORE_NAME    1 (a) 
      13 LOAD_NAME    2 (i) 
      16 LOAD_CONST    0 (0) 
      19 CALL_FUNCTION   1 
      22 LOAD_NAME    2 (i) 
      25 LOAD_CONST    1 (1) 
      28 CALL_FUNCTION   1 
      31 BINARY_ADD   
      32 LIST_APPEND    2 
      35 JUMP_ABSOLUTE   7 
     >> 38 POP_TOP    
      39 LOAD_CONST    2 (None) 
      42 RETURN_VALUE   

Там не создается явная функция и переменная a создается в текущей области. Таким образом, a просочился в текущую область действия.


При таком понимании давайте подходим к вашей проблеме.

>>> i = lambda x: a[x] 
>>> alist = [(1, 2), (3, 4)] 

Теперь, когда вы создаете список с пониманием,

>>> [i(0) + i(1) for a in alist] 
[3, 7] 
>>> a 
(3, 4) 

вы можете увидеть, что a просочилась в текущей области и он по-прежнему связан с последним значением от итерации.

Итак, когда вы повторяете выражение генератора после понимания списка, функция lambda использует пропущенный a. Вот почему вы получаете [7, 7], так как a по-прежнему привязан к (3, 4).

Но если вы сначала итерации выражения генератора, то a будет привязан к значениям от alist и не будет просочиться в текущую область, поскольку выражение генератора станет функцией. Итак, когда функция lambda пытается получить доступ к a, она не может найти ее где-либо. Вот почему он терпит неудачу с ошибкой.

Примечание: Такое же поведение не наблюдается в Python 3.x, поскольку утечка предотвращается путем создания функций для понимания списков. Возможно, вы захотите прочитать об этом в журнале «История Python», From List Comprehensions to Generator Expressions, написанном самим Гвидо.

2

См. Мой другой ответ об обходном пути. Но, думая немного больше, проблема, кажется, немного сложнее. Я думаю, что есть несколько вопросов, здесь происходит:

  • Когда вы i = lambda x: a[x], переменная a не является параметром функции, это называется closure. Это то же самое и для лямбда-выражений, и для определения нормальной функции.

  • Python, по-видимому, выполняет «позднюю привязку», что означает, что значение переменных, которые вы закрыли, просматривается только в тот момент, когда вы вызываете функцию. Это может привести к various неожиданным results.

  • В Python 2 существует разница между перечнями списков, которые вызывают их переменную цикла, и выражения генератора, в которых переменная цикла не протекает (подробнее см. this PEP). Эта разница была удалена в Python 3, где понимание списка является ярлыком для list(generater_expression). Я не уверен, но это, вероятно, означает, что Python2-списки выполняются в своей внешней области, в то время как выражения генераторов и представления Python3 создают свою собственную внутреннюю область.

Демонстрация (в python2):

In [1]: def f(): # closes over a from global scope 
    ...:  return 2 * a 
    ...: 

In [2]: list(f() for a in range(5)) # does not find a in global scope 
[...] 
NameError: global name 'a' is not defined 

In [3]: [f() for a in range(5)] 
# executes in global scope, so f finds a. Also leaks a=8 
Out[3]: [0, 2, 4, 6, 8] 

In [4]: list(f() for a in range(5)) # finds a=8 in global scope 
Out[4]: [8, 8, 8, 8, 8] 

В Python3:

In [1]: def f(): 
    ...:  return 2 * a 
    ...: 

In [2]: list(f() for a in range(5)) 
# does not find a in global scope, does not leak a 
[...]  
NameError: name 'a' is not defined 

In [3]: [f() for a in range(5)] 
# does not find a in global scope, does not leak a 
[...] 
NameError: name 'a' is not defined 

In [4]: list(f() for a in range(5)) # a still undefined 
[...] 
NameError: name 'a' is not defined 
Смежные вопросы