2016-05-03 9 views
6

Ich möchte folgendes tun:für jede (verschachtelte) Funktion mit freien Variablen (rekursiv)

for every nested function f anywhere in this_py_file: 
    if has_free_variables(f): 
     print warning 

Warum? In erster Linie als Versicherung gegen die verspätete Schließung kamcha als described elsewhere. Nämlich:

>>> def outer(): 
...  rr = [] 
...  for i in range(3): 
...   def inner(): 
...    print i 
...   rr.append(inner) 
...  return rr 
... 
>>> for f in outer(): f() 
... 
2 
2 
2 
>>> 

Und wenn ich über eine freie Variable gewarnt bekommen, würde ich entweder eine explizite Ausnahme (im seltenen Fall, dass ich dieses Verhalten möchte) hinzufügen oder es beheben, wie so:

 
...   def inner(i=i): 

Dann wird das Verhalten mehr wie verschachtelte Klassen in Java (wobei jede Variable, die in einer inneren Klasse verwendet werden soll, final sein muss).

(Soweit ich weiß, wird dies nicht nur das späte Bindungsproblem lösen, sondern auch die bessere Verwendung von Speicher fördern, denn wenn eine Funktion einige Variablen in einem äußeren Bereich "schließt", dann kann der äußere Bereich kein Müll sein gesammelt, solange die Funktion in der Nähe ist. Richtig?)

Ich kann keine Möglichkeit finden, Funktionen in anderen Funktionen verschachtelt zu bekommen. Momentan kann ich am besten einen Parser instrumentieren, was sehr viel Arbeit bedeutet.

+0

Was versuchst du genau zu machen? – kpie

+0

Dies ist ein [XY Problem] (http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem) in großem Maßstab. Wenn Sie sich Sorgen über verspätete Schließungen in einem Stück Code machen, rewrite den Code nicht darunter leiden, tun Sie keine massive Versuch-außer-Stil-Sache nach der Tat. Haben Sie keine Kontrolle über den Code, der dies möglicherweise tun könnte? –

+0

Auch Leute haben nach [spätbindenden Schließungen vorher] gefragt (http://stackoverflow.com/questions/6035848/python-closure-not-working-as-expected) und haben Antworten erhalten, die helfen, dasselbe zu verringern. Folgender Anzug würde dir besser dienen als dein gegenwärtiger Ansatz. –

Antwort

2

Betrachten Sie die folgende Funktion verwenden:

def outer_func(): 
    outer_var = 1 

    def inner_func(): 
     inner_var = outer_var 
     return inner_var 

    outer_var += 1 
    return inner_func 

Das __code__ Objekt verwendet werden kann, den Code Aufgabe der inneren Funktion wiederherzustellen:

outer_code = outer_func.__code__ 
inner_code = outer_code.co_consts[2] 

Von diesem Code Objekt können die freien Variablen gewonnen werden:

inner_code.co_freevars # ('outer_var',) 

Sie können, ob überprüfen oder nicht Codeobjekt sollte mit eingesehen werden:

hasattr(inner_code, 'co_freevars') # True 

Nachdem Sie alle Funktionen aus der Datei erhalten, das etwas aussehen könnte:

for func in function_list: 
    for code in outer_func.__code__.co_consts[1:-1]: 
     if hasattr(code, 'co_freevars'): 
      assert len(code.co_freevars) == 0 

Jemand, der mehr über das Innenleben weiß, kann wahrscheinlich eine bessere Erklärung oder eine prägnantere Lösung liefern.

0

Um Ihre verschachtelten Funktionen zu "erzwingen" (auch wenn Sie sie außer Kraft setzen) müssten Sie verwenden, um Variablendefinitionsnamen für jede Deklaration zu erstellen.

def outer(): 
    rr = [] 
    for i in range(3): 
     eval("def inner"+str(i)+"""(): 
      print """+str(i)) 
     rr.append(eval("inner"+str(i))) 
    return rr 

for f in outer(): f() 

druckt

1 
2 
3 
+0

Dies zwingt die Schließung zum Zuweisungszeitpunkt bewertet zu werden, was gut ist, aber nicht immer die richtige Lösung. Stellen Sie sich vor, Sie wollten eine zustandsbehaftete Funktion programmieren, die beispielsweise später nur Datenbankaufrufe im Code ausführen soll - dies würde einen Datenbankaufruf sofort erzwingen, statt später wie geplant. Dies ist ein Hack, keine vollständige Lösung. Guter Versuch, aber: D –

+0

Interessant. Mein Code funktioniert nicht. und hier war ich so selbstsicher ... Das übliche Dilemma. – kpie

+2

Ich habe es nicht erwähnt, aber es sollte klar sein, dass eine echte Lösung eine "set and forget" -Stil-Funktion sein muss. Es wird Teil des Entwicklungsgeschirrs und es gibt absolut keine Möglichkeit, dass es den Codierungsstil beeinflusst oder den bestehenden Code ändert. Selbst das Hinzufügen eines Dekorators zu jeder verschachtelten Funktion wäre zu viel Aufwand. –

-1

Sie import copy benötigen und rr.append(copy.copy(inner))

https://pymotw.com/2/copy/

+0

Dies beantwortet die Frage nicht. Er bittet nicht darum, verspätete Schließungen zu vermeiden, er bittet darum, sie zu entdecken. –

+0

Leider funktioniert das nicht wirklich. Ich denke, verschiedene 'innere() 'Funktionen sind sowieso (in gewissem Sinne) erstellt, aber das Problem ist, dass sie alle eine freie Variable Verweis (mit Begriffen in einem lockeren Sinne) zu den äußeren Bereich, und nur den Wert von "i" in diesem Umfang, wenn sie aufgerufen werden. Zu welcher Zeit ist es "2". Dies würde aus der Sicht des C++/Java-Sprachmodells als seltsame Semantik erscheinen; Das ist mehr wie in JavaScript und vielleicht Ruby. –

0

Ich wollte dies auch in Jython tun. Aber die in der angenommenen Antwort gezeigte Methode funktioniert dort nicht, weil die co_consts nicht für ein Code-Objekt verfügbar ist. (Außerdem scheint es keine andere Möglichkeit zu geben, ein Code-Objekt abzufragen, um an die Code-Objekte verschachtelter Funktionen zu gelangen.)

Aber natürlich sind die Code-Objekte irgendwo da, wir haben die Quelle und vollen Zugriff, es ist also nur eine Frage der Suche nach einem einfachen Weg innerhalb einer angemessenen Zeit. Also hier ist eine Möglichkeit, die funktioniert. (Halten Sie Ihre Sitze.)

Angenommen, wir haben Code wie folgt in Modul mod:

def outer(): 
    def inner(): 
     print "Inner" 

zuerst den Code Objekt der äußeren Funktion erhalten direkt:

code = mod.outer.__code__ 

In Jython, Dies ist eine Instanz von PyTableCode, und durch das Lesen der Quelle finden wir, dass die tatsächlichen Funktionen in einer Java-Klasse implementiert sind, die aus Ihrem gegebenen Skript besteht, auf das durch das Feld funcs des Codeobjekts verwiesen wird. (Alle diese Klassen, die aus Skripten bestehen, sind Unterklassen von PyFunctionTable, daher ist das der deklarierte Typ von funcs.) Dies ist von Jython aus nicht sichtbar, als Folge einer magischen Maschinerie, mit der ein Designer angibt, dass Sie auf diese zugreifen Dinge auf eigenes Risiko.

Also müssen wir für einen Moment in das Java eintauchen. Eine Klasse wie folgt funktioniert der Trick:

import java.lang.reflect.Field; 

public class Getter { 
    public static Object getFuncs(Object o) 
    throws NoSuchFieldException, IllegalAccessException { 
     Field f = o.getClass().getDeclaredField("funcs"); 
     f.setAccessible(true); 
     return f.get(o); 
    } 
} 

Zurück in Jython:

>>> import Getter 
>>> funcs = Getter.getFuncs(mod.outer.__code__) 
>>> funcs 
[email protected] 

Nun, dies funcs Objekt alle Funktionen erklärt überall im Jython Skript hat (die beliebig verschachtelt, innerhalb von Klassen, etc.). Außerdem enthält es Felder, die die entsprechenden Code-Objekte enthalten.

>>> fields = funcs.class.getDeclaredFields() 

In meinem Fall das Codeobjekt an die verschachtelte Funktion entspricht, ist nun mal die letzte sein:

>>> flast = fields[-1] 
>>> flast 
static final org.python.core.PyCode mod$py.inner$24 

den Code Objekt von Interesse zu erhalten:

>>> flast.setAccessible(True) 
>>> inner_code = flast.get(None) #"None" because it's a static field. 
>>> dir(inner_code) 
co_argcount co_filename co_flags co_freevars co_name co_nlocals co_varnames 
co_cellvars co_firstlineno 

Und die Rest ist das gleiche wie die akzeptierte Antwort, dh überprüfen Sie co_freevars, (das ist in Jython, im Gegensatz zu co_consts).

Eine gute Sache über diesen Ansatz ist, dass Sie genau alle Code-Objekte aufzählen, die irgendwo innerhalb der Quellcode-Datei deklariert sind: Funktionen, Methoden, Generatoren, ob sie unter oder unter einander verschachtelt sind. Es gibt keinen anderen Ort, an dem sie sich verstecken könnten.

Verwandte Themen