Frage

Während ich untersucht wurde ein problem hatte ich mit lexikalischen closures in Javascript-code, kam ich dieses problem in Python:

flist = []

for i in xrange(3):
    def func(x): return x * i
    flist.append(func)

for f in flist:
    print f(2)

Beachten Sie, dass dieses Beispiel bewusst vermeidet lambda.Es druckt "4 4 4", die ist überraschend.Ich würde erwarten, dass "0 2 4".

Dies entspricht Perl-code macht es richtig:

my @flist = ();

foreach my $i (0 .. 2)
{
    push(@flist, sub {$i * $_[0]});
}

foreach my $f (@flist)
{
    print $f->(2), "\n";
}

"0 2 4" gedruckt wurde.

Können Sie bitte erläutern Sie den Unterschied ?


Update:

Das problem nicht mit i als die Globale.Dies zeigt das gleiche Verhalten:

flist = []

def outer():
    for i in xrange(3):
        def inner(x): return x * i
        flist.append(inner)

outer()
#~ print i   # commented because it causes an error

for f in flist:
    print f(2)

Wie der Kommentar zeigt, i unbekannt ist an diesem Punkt.Immer noch, es druckt "4 4 4".

War es hilfreich?

Lösung

Python ist tatsächlich verhält sich wie definiert. Drei separate Funktionen erstellt werden, aber Sie haben jeweils die Schließung der Umwelt, von der Sie definiert sind in - in diesem Fall in der globalen Umgebung (oder der äußeren Funktion ist Umgebung, wenn die Schleife gelegt wird, die innerhalb einer anderen Funktion).Das ist genau das problem, obwohl - in diesem Umfeld, ich mutiert ist, und die Verschlüsse aller beziehen sich auf die gleiche ich.

Hier ist die beste Lösung, die ich mit oben kommen kann - eine Funktion hat, und rufen Sie dass statt.Dies zwingt verschiedene Umgebungen für jede der Funktionen erstellt, mit einem andere ich in jedem.

flist = []

for i in xrange(3):
    def funcC(j):
        def func(x): return x * j
        return func
    flist.append(funcC(i))

for f in flist:
    print f(2)

Dies ist, was passiert, wenn Sie mix Nebenwirkungen und funktionale Programmierung.

Andere Tipps

Die Funktionen in der Schleife definiert immer Zugriff auf die gleiche Variable i während sich sein Wert ändert. Am Ende der Schleife, alle Funktionen auf die gleiche Variable Punkt, der den letzten Wert in der Schleife hält. Der Effekt ist, was in dem Beispiel berichtete

Um i zu bewerten und seinen Wert zu verwenden, ist ein gemeinsames Muster als Parameter Standard festlegen: Parameter Standardwerte ausgewertet werden, wenn die def Anweisung ausgeführt wird, und somit der Wert des Schleifenvariable eingefroren

Die folgenden Arbeiten wie erwartet:

flist = []

for i in xrange(3):
    def func(x, i=i): # the *value* of i is copied in func() environment
        return x * i
    flist.append(func)

for f in flist:
    print f(2)

Hier ist, wie Sie es tun, die functools Bibliothek (was ich bin nicht sicher, die Frage zu dem Zeitpunkt zur Verfügung gestellt wurde).

from functools import partial

flist = []

def func(i, x): return x * i

for i in xrange(3):
    flist.append(partial(func, i))

for f in flist:
    print f(2)

Ausgänge 0 2 4, wie erwartet.

Blick auf diese:

for f in flist:
    print f.func_closure


(<cell at 0x00C980B0: int object at 0x009864B4>,)
(<cell at 0x00C980B0: int object at 0x009864B4>,)
(<cell at 0x00C980B0: int object at 0x009864B4>,)

Es bedeutet, dass sie alle auf die gleiche i variable Instanz, die einen Wert von 2, wenn die Schleife beendet ist haben wird.

Eine lesbare Lösung:

for i in xrange(3):
        def ffunc(i):
            def func(x): return x * i
            return func
        flist.append(ffunc(i))

Was geschieht, ist, dass die Variable i erfasst, und die Funktionen den Wert zurückkommen es an der Zeit gebunden ist es genannt wird. In funktionalen Sprachen diese Art von Situation entsteht nie, wie ich nicht Rebound würde. Jedoch mit Python, und auch, wie Sie mit Lisp gesehen haben, ist dies nicht mehr der Fall.

Der Unterschied mit Ihrem Schema Beispiel ist mit der Semantik der do-Schleife zu tun. Scheme effektiv ein neues Variable i jedes Mal durch die Schleife zu schaffen, anstatt Wiederverwendung eines bestehenden i wie bei den anderen Sprachen verbindlich. Wenn Sie eine andere Variable erstellt außerhalb der Schleife und mutieren sie verwenden, werden Sie das gleiche Verhalten in Schema sehen. Versuchen Sie ersetzen Ihre Schleife mit:

(let ((ii 1)) (
  (do ((i 1 (+ 1 i)))
      ((>= i 4))
    (set! flist 
      (cons (lambda (x) (* ii x)) flist))
    (set! ii i))
))

Hier finden Sie aktuelle hier für einige weitere Diskussion über diese.

[Bearbeiten] Vielleicht ein besserer Weg, es zu beschreiben, ist die do-Schleife als Makro zu denken, die die folgenden Schritte ausführt:

  1. ein Lambda definiert einen einzigen Parameter (i), mit einem Körper, durch den Körper der Schleife definiert, wobei,
  2. Ein sofortiger Aufruf dieses Lambda mit dem entsprechenden Werten von i als Parameter.

dh. das entspricht den unter Python:

flist = []

def loop_body(i):      # extract body of the for loop to function
    def func(x): return x*i
    flist.append(func)

map(loop_body, xrange(3))  # for i in xrange(3): body

Die i nicht mehr ist die von dem übergeordneten Bereich, sondern eine ganz neue Variable in einem eigenen Rahmen (dh. Die Parameter auf das Lambda) und so bekommen Sie das Verhalten, das Sie beobachten. Python nicht über diesen impliziten neuen Bereich hat, so dass der Körper der for-Schleife teilt nur die Variable i.

Ich bin immer noch nicht ganz überzeugt, warum in einigen Sprachen das eine Art und Weise funktioniert, und in einigen anderen Weg. In Common Lisp ist es wie Python:

(defvar *flist* '())

(dotimes (i 3 t)
  (setf *flist* 
    (cons (lambda (x) (* x i)) *flist*)))

(dolist (f *flist*)  
  (format t "~a~%" (funcall f 2)))

Prints "6 6 6" (beachten Sie, dass hier die Liste von 1 bis 3 ist, und umgekehrt "gebaut). Während in Schema funktioniert es wie in Perl:

(define flist '())

(do ((i 1 (+ 1 i)))
    ((>= i 4))
  (set! flist 
    (cons (lambda (x) (* i x)) flist)))

(map 
  (lambda (f)
    (printf "~a~%" (f 2)))
  flist)

Prints "6 4 2"

Und wie ich bereits erwähnt habe, ist Javascript in Python / CL-Camp. Es scheint, gibt es eine Implementierung Entscheidung hier, die verschiedenen Sprachen in unterschiedlicher Weise nähern. Ich würde gerne verstehen, was die Entscheidung ist, genau.

Das Problem ist, dass alle lokalen Funktionen der gleichen Umgebung binden und damit auf die gleiche i variabel. Die Lösung (Umgehung) getrennt Umgebungen (Stack-Frames) für jede Funktion (oder Lambda) zu erstellen:

t = [ (lambda x: lambda y : x*y)(x) for x in range(5)]

>>> t[1](2)
2
>>> t[2](2)
4

Die Variable i ist ein globales, deren Wert 2 zu jedem Zeitpunkt die Funktion f aufgerufen wird.

Ich würde geneigt sein, das Verhalten sind Sie nach zu implementieren, wie folgt:

>>> class f:
...  def __init__(self, multiplier): self.multiplier = multiplier
...  def __call__(self, multiplicand): return self.multiplier*multiplicand
... 
>>> flist = [f(i) for i in range(3)]
>>> [g(2) for g in flist]
[0, 2, 4]

Antwort auf Ihr Update : Es ist nicht die Globalität von i per se , die dieses Verhalten verursachen, dann ist es die Tatsache, dass es sich um eine Variable aus einem umgebenden Gültigkeitsbereich ist, der eine hat fester Wert über die Zeit, als f bezeichnet wird. In Ihrem zweiten Beispiel wird der Wert von i vom Umfang der kkk Funktion genommen wird, und nichts ändert sich, wenn Sie die Funktionen auf flist nennen.

Die Argumentation hinter dem Verhalten wird bereits erläutert, und mehrere Lösungen geschrieben wurden, aber ich denke, das ist die pythonic (denken Sie daran, alles in Python ist ein Objekt!):

flist = []

for i in xrange(3):
    def func(x): return x * func.i
    func.i=i
    flist.append(func)

for f in flist:
    print f(2)

Claudiu Antwort ist ziemlich gut, einen Funktionsgenerator verwenden, aber Piro Antwort ist ein Hack, um ehrlich zu sein, da es i in ein „verstecktes“ Argument mit einem Standardwert ist zu machen (es wird funktionieren, aber es ist nicht " pythonic ").

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top