2012-04-06 15 views
1

Meine Absicht ist es, ein Wörterbuch zu erstellen, dessen Schlüssel Primitive sind und deren Werte Null-Argument-Funktionen sind, die Strings zurückgeben. (Dies ist Teil eines größeren Projekts zum Implementieren einer VM.) Einige dieser Funktionen sind nicht trivial und werden manuell erstellt und zugewiesen. Die funktionieren gut. Andere scheinen jedoch der automatischen Erzeugung zugänglich zu sein.Wie erzwinge Python3 nach Wert übergeben?

Mein erster Versuch fehlgeschlagen:

>>> regs = ['a', 'b', 'c', 'x', 'y', 'z'] 
>>> vals = {i : lambda: r for i, r in enumerate(regs)} 
>>> [(k, vals[k]()) for k in vals.keys()] 
[(0, 'z'), (1, 'z'), (2, 'z'), (3, 'z'), (4, 'z'), (5, 'z')] 

OK, fein; Die Lambda-Funktion liest r nicht, bis sie aufgerufen wird. Ich versuchte es noch einmal und versucht, einen Wert für sich allein zu isolieren:

>>> from copy import copy 
>>> vals = {} 
>>> i = 0 
>>> for reg in regs: 
...  r = copy(reg) # (1) 
...  vals[i] = lambda: r 
...  i += 1 
... 
>>> [(k, vals[k]()) for k in vals.keys()] 
[(0, 'z'), (1, 'z'), (2, 'z'), (3, 'z'), (4, 'z'), (5, 'z')] 

(1) ich diesen Schritt gedacht, eine unabhängige Variable erstellen, die sich nicht ändern würde, wenn reg tat. Das ist nicht der Fall.

So hat dieser Versuch eindeutig nicht funktioniert. Vielleicht ist die Kopie an einer Schnur ein Noop?

>>> 's' is 's' 
True 
>>> a = 's' 
>>> b = copy(a) 
>>> a is b 
True 
>>> from copy import deepcopy 
>>> b = deepcopy(a) 
>>> a is b 
True 

Richtig. Kopieren an einer Schnur ist ein Noop. Deepcopy repariert das nicht. Folglich hat das Lambda immer noch einen Verweis auf die Variable, die in jeder Schleife aktualisiert wird, was diesen Fehler verursacht.

Wir brauchen einen anderen Ansatz. Was passiert, wenn ich die Variable, die ich möchte, in eine statische Variable der temporären Funktion speichere? Das sollte funktionieren, wenn jede temporäre Funktion ihre eigene Identität bekommt ...

>>> vals = {} 
>>> i = 0 
>>> for reg in regs: 
...  def t(): 
...    return t.r 
...  t.r = reg 
...  vals[i] = t 
...  i += 1 
... 
>>> [(k, vals[k]()) for k in vals.keys()] 
[(0, 'z'), (1, 'z'), (2, 'z'), (3, 'z'), (4, 'z'), (5, 'z')] 

Nein. An diesem Punkt bin ich kurz davor, alles nur manuell zu behandeln:

>>> vals = {} 
>>> vals[0] = lambda: 'a' 
>>> vals[1] = lambda: 'b' 

... und so fort. Das fühlt sich jedoch an, als ob man aufgibt und wird unglaublich langweilig werden. Gibt es einen richtigen phytonischen Weg, um diese Aufgabe zu erfüllen? Einer der Gründe, warum ich Python normalerweise liebe, ist, dass ich mich von der manuellen Zeigerverwaltung fern halte; Ich hätte mir nie vorstellen können, dass ich mir wünschen würde, dass es eine ganze Reihe von Zeigerwerkzeugen enthält!

Antwort

2

Verschlüsse nie kopieren, kopieren sie weder Werte noch Referenzen. Stattdessen erinnern sie sich an den Umfang und die Variable, die sie verwenden, und gehen immer dorthin zurück. Dasselbe gilt für einige andere Sprachen, wie C#, JavaScript, IIRC Lisp und wahrscheinlich mehr. Das Gleiche gilt für mehrere andere Sprachen. Dies ist wichtig für einige fortgeschrittene Anwendungsfälle (grundsätzlich jedes Mal, wenn mehrere Schließungen den Zustand teilen sollen), aber es kann einen beißen. Zum Beispiel:

x = 1 
def f(): return x 
x = 2 
assert f() == 2 

Da nur Python neue Bereiche für Funktionen erstellt (und Klassen und Module, aber das spielt hier keine Rolle), die Schleifenvariable reg ist nur einmal vorhanden und damit alle Verschlüsse beziehen sich auf die gleiche Variable. Wenn sie nach der Schleife aufgerufen werden, sehen sie daher den letzten Wert, den die Variable annimmt.

Das gleiche ist wahr, nur dieses Mal ist es die t, die geteilt wird - Sie erstellen N separate Schließungen, jedes mit dem richtigen Wert in seinem r Attribut. Aber wenn sie aufgerufen werden, suchen sie t im umschließenden Geltungsbereich und erhalten somit immer einen Verweis auf die letzte von Ihnen erstellte Schließung.

Es gibt mehrere Problemumgehungen.Man drückt die Schließung Schöpfung in eine separate Funktion, die zwingt, einen neuen, engagierten Raum für jede Schließung zu verweisen:

def make_const(x): 
    def const(): 
     return x 
    return const 

Eine andere Möglichkeit (knappere aber dunkler) wird (ab-) unter Verwendung der Tatsache, dass Standard Parameter werden zur Definitionszeit gebunden:

for reg in regs: 
    t = lambda reg=reg: reg 

In anderen Fällen Sie functools verwenden können, aber es scheint nicht hier anzuwenden.

+0

Danke! Das Einstellen von Standardparametern ist für mich viel sinnvoller, als alles über eine make_const-Funktion zu tun, zumindest soweit ich weiß, wie es funktioniert. Ich nehme an, dass es keinen signifikanten Unterschied in der Leistung zwischen den zwei Methoden gibt? – coriolinus

+0

@coriolinus Es sollte keinen signifikanten Unterschied geben. Wenn es da ist, gibt es wahrscheinlich viele größere Fische, bevor es zum Flaschenhals wird. Und an diesem Punkt sollten Sie wahrscheinlich nur in den sauren Apfel beißen, Ihren Code [RPython] (http://morepypy.blogspot.de/2011/04/tutorial-writing-interpreter-with-pypy.html) erstellen und eine Kompilierung machen C, ein JIT-Compiler und eine Reihe von guten GCs fast kostenlos. – delnan

Verwandte Themen