20

Ich habe eine Liste und eine lambda Funktion definiert alsUnexpected Ausgabe aus Liste (Generator)

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

Dann versuche ich zwei verschiedene Methoden, um eine einfache Summe

Erste Methode zu berechnen.

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

Zweite Methode.

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

Beide Ergebnisse sind unerwartet unterschiedlich. Warum passiert das?

+0

es zeigt mir Fehler. –

+0

@AvinashRaj Das Ausführen der zweiten Methode gibt zuerst 'NameError: globaler Name 'a' ist nicht definiert ' –

+1

Dort liegt Ihr Problem, ein wird in der ersten als (3, 4) definiert, dann die list() -Funktion immer das 'a' – TheGeorgeous

Antwort

15

Dieses Verhalten wurde in Python 3 behoben. Wenn Sie ein Listenverständnis [i(0) + i(1) for a in alist] verwenden, definieren Sie a in seinem umgebenden Bereich, der für i zugänglich ist. In einer neuen Sitzung wird list(i(0) + i(1) for a in alist) Fehler werfen.

>>> 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 

Eine Liste Verständnis ist kein Generator: Generator expressions and list comprehensions.

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

In Ihrem Beispiel list() als eine Klasse ihren eigenen Umfang von Variablen hat und Zugriff auf globale Variablen höchstens hat. Wenn Sie das verwenden, wird i in diesem Bereich nach a suchen. Versuchen Sie, diese in neue Sitzung:

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

es dazu Vergleichen in einer anderen Sitzung:

>>> 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 

Wenn Sie list(i(0) + i(1) for a in alist) laufen Sie einen Generator (i(0) + i(1) for a in alist) an die list Klasse übergeben, die es versuchen, es zu konvertieren eine Liste in einem eigenen Umfang, bevor die Liste zurückgegeben wird. Für diesen Generator, der innerhalb der Lambda-Funktion keinen Zugriff hat, hat die Variable a keine Bedeutung.

Das Generatorobjekt <generator object <genexpr> at 0x10e60db90> hat den Variablennamen a verloren. Wenn dann list versucht, den Generator aufzurufen, löst die Lambda-Funktion einen Fehler für undefined a aus.

Das Verhalten der Listenkomprehensionen im Gegensatz zu Generatoren erwähnt auch 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.

In 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

Wie produziert es die Ausgabe für beide? –

+0

@AvinashRaj: indem man zuerst das Listenverständnis ausführt, ist 'a' immer noch an das' (3, 4) 'Tupel gebunden. –

1

a in globalen Bereich ist. So sollte es Fehler

Lösung geben ist:

i = lambda a, x: a[x]

5

Sie sollten a ein Parameter auf Ihre Lambda-Funktion machen.Dies funktioniert wie erwartet:

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

EDIT diese Antwort nur eine einfache Abhilfe ist, und ist keine wirkliche Antwort auf die Frage:

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] 

Eine alternative Möglichkeit, das gleiche Ergebnis wäre zu bekommen. Der beobachtete Effekt ist etwas komplexer, siehe meine other answer.

1

Nachdem [i(0) + i(1) for a in alist] ausgeführt wurde, wird a(3,4).

Dann, wenn die folgende Zeile ausgeführt:

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

(3,4) Wert verwendet wird, sowohl die Zeit von der Lambda-Funktion i als Wert a, so druckt er [7,7].

Stattdessen sollten Sie Ihr Lambda definieren Funktionen mit zwei Parametern a und x.

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

Wichtige Dinge hier zu verstehen sind

  1. Generator Ausdrücke werden Funktion werden Erstellen von Objekten intern aber Liste Verständnis nicht.

  2. Beide binden die Schleifenvariable an die Werte und die Schleifenvariablen sind im aktuellen Bereich, wenn sie nicht bereits erstellt wurden.

Hier können die Byte-Codes des Generators Ausdruck siehe

>>> 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   

Es lädt den Code Objekt und dann macht es eine Funktion. Sehen wir uns das eigentliche Code-Objekt an.

>>> 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   

Wie Sie hier sehen, wird der aktuelle Wert aus dem Iterator in den Variablen a gespeichert. Aber da wir dies zu einem Funktionsobjekt machen, ist die erzeugte a nur innerhalb des Generatorausdrucks sichtbar.

Aber im Falle der Liste Verständnis,

>>> 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   

Es gibt keine explizite Funktion Erstellung und die Variable a wird im aktuellen Bereich erstellt. Also, a ist in den aktuellen Umfang durchgesickert.


Mit diesem Verständnis können wir Ihr Problem angehen.

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

Wenn Sie nun eine Liste mit Verständnis schaffen,

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

Sie können sehen, dass a auf den aktuellen Bereich durchgesickert ist, und es ist immer noch auf den letzten Wert der Iteration gebunden.

Also, wenn Sie den Generator Ausdruck nach dem Listenverständnis durchlaufen, verwendet die lambda Funktion die durchgesickerten a. Deshalb erhalten Sie [7, 7], da a immer noch an (3, 4) gebunden ist.

Wenn Sie jedoch zuerst den Generatorausdruck iterieren, wird a an die Werte von alist gebunden und nicht an den aktuellen Gültigkeitsbereich weitergegeben, da der Generatorausdruck eine Funktion wird. Also, wenn die lambda Funktion versucht, auf a zuzugreifen, konnte es nirgends finden. Deshalb scheitert es mit dem Fehler.

Hinweis: Das gleiche Verhalten kann in Python 3.x nicht beobachtet werden, da das Lecken verhindert wird, indem Funktionen für List-Comprehensions ebenfalls erstellt werden. Vielleicht möchten Sie mehr darüber in der Geschichte von Python Blog-Beitrag lesen, From List Comprehensions to Generator Expressions, von Guido selbst geschrieben.

2

Siehe meine andere Antwort für eine Problemumgehung. Aber wenn man ein bisschen darüber nachdenkt, scheint das Problem etwas komplexer zu sein. Ich denke, es gibt einige Probleme hier los:

  • Wenn Sie i = lambda x: a[x] tun, wird die Variable a kein Parameter ist auf die Funktion, das ist ein closure genannt. Dies gilt sowohl für Lambda-Ausdrücke als auch für normale Funktionsdefinitionen.

  • Python hat anscheinend eine 'späte Bindung', was bedeutet, dass der Wert der Variablen, die Sie geschlossen haben, nur in dem Moment nachgeschlagen wird, in dem Sie die Funktion aufrufen. Dies kann zu various unerwarteten results führen.

  • In Python 2 gibt es einen Unterschied zwischen Listenkompressen, die ihre Schleifenvariable verlieren, und Generatorausdrücken, bei denen die Schleifenvariable nicht leckt (Details siehe). Dieser Unterschied wurde in Python 3 entfernt, wo ein Listenverständnis eine Verknüpfung für list(generater_expression) ist. Ich bin mir nicht sicher, aber das bedeutet wahrscheinlich, dass Python2-List-Comprehensions in ihrem äußeren Umfang ausgeführt werden, während Generator-Ausdrücke und Python3-Listen-Comprehensions ihren eigenen inneren Geltungsbereich erzeugen.

Demonstration (in 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] 

In 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