Closures

Eine Sache, die mich beim Programmieren der Metaklassen in garbledina mehrmals gestört hat, ist, dass Python ein sehr seltsames Verhalten zeigt, wenn man Funktionen in for-Schleifen definiert. Abhängig davon, wie sehr ich darüber nachdenke, verstehe ich manchmal, warum das so ist, und manchmal nicht:

def f():
    functions = []
    for i in xrange(10):
        def g():
            return i**2
        functions.append(g)
    return functions

functions = f()

print map(apply, functions)
# [81, 81, 81, 81, 81, 81, 81, 81, 81, 81]

Eigentlich hätte ich hier gerne [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] als Ausgabe gehabt. Das Problem ist, dass Python nur ein Closure baut (in Python repräsentiert durch cell-Objekte):

for g in functions:
    print g.func_closure

Das Problem hat mich jedenfalls dazu angeregt, mich etwas näher mit Python-Bytecode zu beschäftigen. Wer mehr dazu wissen möchte, der schaue in meinen del.icio.us-Feed. Herausgekommen ist ein Decorator, der für Python-Funktionen die Closure-Variablen verändern kann. Zusätzlich kann er auch globale Variablen als Closures umbinden. Obiges Beispiel sieht dann also folgendermaßen aus:

def f():
    functions = []
    for i in xrange(10):
        @closure(i=i)
        def g():
            return i**2
        functions.append(g)
    return functions

functions = f()

print map(apply, functions)
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

Wie gesagt, auch folgendes funktioniert:

@closure(a="Hello, world")
def g():
  print a

Und da nur eine Referenz und gespeichert wird, hat das auch folgende lustige Effekte:

@closure(a=[])
def g():
  a.append(len(a))
  print a

g() # -> [0]
g() # -> [0, 1]
g() # -> [0, 1, 2]

a verhält sich hier für g quasi wie eine statische Variable in C, neu binden ist aber nicht möglich. Vielleicht implementiere ich das noch. Dann aber wahrscheinlich unter einem anderen Namen, da die Funktionalität dann doch stark verschieden wäre.

Der Code dazu wird sich irgendwann in garbledina wiederfinden. Wahrscheinlich irgendwo unter garbledina.util.

Übrigens würde mich sehr interessieren, wie sich das erste Beispiel bei anderen Programmiersprachen, bei denen Funktionen First-Class-Objekte sind, z.B. LISP oder Dylan, verhält. (Kommentare gerne per Jabber an mich.)