code mit compile und exec:

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Francesco
User
Beiträge: 824
Registriert: Mittwoch 1. Dezember 2004, 12:35
Wohnort: Upper Austria

folgender code (ich habe ihn so einfach gemacht wies ging) sollte zur laufzeit ausgeführt werden

execode.py

Code: Alles auswählen

class Class1():
    def __init__(self):
        print "class1"

class Class2():

    def __init__(self):
        print "class2"
        cl1 = Class1()

cl = Class2()
1) rufe ich den direkt auf, dann gehts

Code: Alles auswählen

f = open("execode.py", 'r')
scripttext = f.read()
f.close()

code = compile(scripttext + '\n', "~/tmp.txt", 'exec')
exec(code)
2) von einem größeren programm aus als exec ausgefüht (drpython als script) aus ist die Ausgabe eigenartig:

2 a) so geht s

Code: Alles auswählen

class Class1():
    def __init__(self):
        print "class1"

class Class2():

    def __init__(self, oldclass):
        print "class2"
        #cl1 = Class1()

cl = Class1()
cl = Class2(self)
2b) so geht nicht (ist eigentlich das gleiche wie bei 1))

(das eigenartige ist, dass oben (und unten), wenn "heraussen" das Class1 angelegt wird, es klappt. wird ein Class1 Objekt jedoch IN Class2 angelegt (versucht), dann kommt die untenstehende Meldung mit Name Error. :x

Code: Alles auswählen

class Class1():
    def __init__(self):
        print "class1"

class Class2():

    def __init__(self, oldclass):
        print "class2"
        cl1 = Class1()

cl = Class1()
cl = Class2(self)

Code: Alles auswählen

    cl1 = Class1()
<type 'exceptions.NameError'>: global name 'Class1' is not defined
Wie kann es da zu einem Name Error kommen?
BlackJack

@Francesco: Das sieht für mich so ein wenig danach aus, als wenn die betreffende Zeile mit einem Tab eingerückt ist. Dann wäre es nur eine eine Ebene, würde aber trotzdem bei 8 Spaces pro Tab korrekt aussehen wenn sonst 4 echte Leerzeichen pro Ebene verwendet wurden.
Francesco
User
Beiträge: 824
Registriert: Mittwoch 1. Dezember 2004, 12:35
Wohnort: Upper Austria

BlackJack hat geschrieben:@Francesco: Das sieht für mich so ein wenig danach aus, als wenn die betreffende Zeile mit einem Tab eingerückt ist. Dann wäre es nur eine eine Ebene, würde aber trotzdem bei 8 Spaces pro Tab korrekt aussehen wenn sonst 4 echte Leerzeichen pro Ebene verwendet wurden.
Danke für die Antwort, aber das ist es nciht, es ist alles mit spaces eingerückt.
ms4py
User
Beiträge: 1178
Registriert: Montag 19. Januar 2009, 09:37

Bei mir kommt der zu erwartende Fehler (ohne compile/exec):

Code: Alles auswählen

Traceback (most recent call last):
  File "test.py", line 12, in <module>
    cl = Class2(self)
NameError: name 'self' is not defined
Edit: Und so funktioniert es.

Code: Alles auswählen

class Class1():
    def __init__(self):
        print "class1"

class Class2():

    def __init__(self, oldclass):
        print "class2"
        cl1 = Class1()

cl = Class1()
cl = Class2(cl)
Deine Fehlermeldung ergibt IMO kein Sinn...

Edit2: `NameError` gibt auf jeden Fall Sinn, vielleicht kann durch das compile/exec die Fehlermeldung nicht exakt wiedergegeben werden.
„Lieber von den Richtigen kritisiert als von den Falschen gelobt werden.“
Gerhard Kocher

http://ms4py.org/
Francesco
User
Beiträge: 824
Registriert: Mittwoch 1. Dezember 2004, 12:35
Wohnort: Upper Austria

Danke.

Jetzt habe ich ein kurzes, nicht sehr schönes Beispiel zusammengebastelt, um es reproduzieren zu könne. Solange das ganze heraussen definiert wird (unterer Teil des zweiten Listings), gehts. Passiert das aber innerhalb einer Klasse (wie im oberen Teil des zweiten Listings) gehts nicht mehr.

scriptgehtnichtclass.py:

Code: Alles auswählen

class Class1():
    def __init__(self):
        print "class1"

class Class2():
    def __init__(self):
        print "class2"
        cl1 = Class1()

cl = Class2()
Aufrufprogramm:

Code: Alles auswählen

############# geht nicht
class cl():
    def __init__(self):
        f = open("scriptgehtnichtclass.py")

        scripttext = f.read()
        f.close()

        code = compile(scripttext + '\n', "~/tmp.txt", 'exec')
        exec(code)

a= cl()

############# geht 

#f = open("scriptgehtnichtclass.py")
#
#scripttext = f.read()
#f.close()
#
#code = compile(scripttext + '\n', "~/tmp.txt", 'exec')
#exec(code)
kann aber nicht im Sinn des Erfinders sein, oder? :roll: Wenn doch: wie gäbe es da einen workaround? workaround: nicht in der klasse selbst exec aufrufen: zählt nicht. :) (weil ich das ganze in drpython brauche, und da ist das ganze exec(...) in einer Klasse eingebettet). Eine Klasse geht, eine zweite, die in der ersten angelegt wird, anscheinend nicht. Ich möchte aber deswegen nicht das Script extra so umgestalten müssen. :( (ausser es geht wirklich nicht anders) ;)
BlackJack

@Francesco: Das ist noch nicht das minimale Beispiel. Statt der Klasse hättest Du auch einfach das ``exec`` in einer Funktion versuchen können: das geht auch nicht. Aus dem einfachen Grund das der Compiler schon wissen muss welche Variablen in einer Funktion verwendet werden. Dein Code würde ja zur Laufzeit versuchen die Namen `Class1` und `Class2` innerhalb der Funktion zu definieren und das geht halt nicht.

Sauberste Lösung IMHO wäre den Code gar nicht mit ``exec`` auszuführen, sondern die Datei mit dem `imp`-Modul zu importieren.

Ansonsten müsstest Du einfach den Kontext in dem das ganze ausgeführt werden soll explizit angeben:

Code: Alles auswählen

SOURCE = """\
class Class1():
    def __init__(self):
        print 'class1'

class Class2():
    def __init__(self):
        print 'class2'
        cl1 = Class1()

cl = Class2()
"""


class Class():
    def __init__(self):
        namespace = dict()
        exec SOURCE + '\n' in namespace
        cl = namespace['cl']
        # irgendwas mit `cl` machen...


def main():
    obj = Class()


if __name__ == '__main__':
    main()
Das ist sowieso sauberer weil das Modul so bei der Ausführung nicht ausversehen etwas an den lokalen Variablen der `__init__()` verändern kann.
Francesco
User
Beiträge: 824
Registriert: Mittwoch 1. Dezember 2004, 12:35
Wohnort: Upper Austria

Danke Blackjack. Ganz checken tu ich das ehrlich gesagt noch nicht:
weil hier wird das ja auch so definiert:

Code: Alles auswählen

class class_above():
  def __init__(self):
      pass

  def myfunc(self):

    class Class1():
        def __init__(self):
            print "class1"

    class Class2():
        def __init__(self):
            print "class2"
            self.cl1 = Class1()

    c1 = Class1()
    c2 = Class2()

obj = class_above()
obj.myfunc()
und das funktioniert. Ist das das was du gemeint, hast zur "Laufzeit" und nicht wie hier "vordefiniert"?

Ich dachte mit exec wäre das so schön dynamisch. Aber eigentlich ist es von der Funktionaliät (bzw. Flexibilität, was wichtiger für mich ist), egal, ob jetzt ein file eingelesen wird und dann ausgeführt wird oder mit import. Ich müsste den ganzen Codeabschnitt posten. Ich poste den einmal, so lange ist der eh nicht. Auszug aus Drpython:

gekürzter Auszug aus drScriptMenu.py, das ganze ist in einer klasse definiert:

Code: Alles auswählen

class drScriptMenu(drMenu):

Code: Alles auswählen

def RunScript(self, scriptfname):
        f = open(scriptfname, 'r')
        scripttext = f.read()
        f.close()

        if scripttext.find("DrFilename") > -1:
            scripttext = scripttext.replace("DrFilename", "self.parent.txtDocument.filename")
        if scripttext.find("DrScript") > -1:
            scripttext = scripttext.replace("DrScript.", "self.parent.DrScript.")
        if scripttext.find("DrDocument") > -1:
            scripttext = scripttext.replace("DrDocument", "self.parent.txtDocument")
        if scripttext.find("DrPrompt") > -1:
            scripttext = scripttext.replace("DrPrompt", "self.parent.txtPrompt")
        if scripttext.find("DrFrame") > -1:
            scripttext = scripttext.replace("DrFrame", "self.parent")

        try:
            code = compile(scripttext + '\n', "~/tmp.txt", 'exec')
        except:
            drScrolledMessageDialog.ShowMessage(self.parent, ("Error compiling script."),
                "Error", wx.DefaultPosition, (550,300))
            return


        try:
            exec(code)
        except:
            drScrolledMessageDialog.ShowMessage(self.parent, "Error running script:", "Error", wx.DefaultPosition, (550,300))
            return

Hier werden einige Variablen ersetzt (um das ganze bedienerfreundlicher zu machen). Wenn man das ganze mit import ersetzt, müsste es gehen(?)

oder ich versuche das mit dem namespace, wie Du vorgeschlagen hast. Nachtrag: das passt in meinem Fall auch nicht recht, weil ich ja direkt in dem script, das aufgerufen wird, was machen möchte, und nicht ausserhalb (also in der klasse drscriptmenu, denn das sollte ja das ganz nur durchführen, egal was mit exec script dann definiert ist).

Jetzt ergibt sich noch eine Frage: Warum wird das bei mir vorher "kompiliert" und dieser Code mit exec ausgeführt (wahrscheinlich, um eventuelle Fehler im eingebetteten CodeFehler noch vorher anzuzeigen), bei dir aber gleich mit exec. Also kann exec vorcompilierten als auch direkten source code auswerten?
BlackJack

@Francesco: Wenn Du die beiden Klassen direkt im Quelltext in der `myfunc()` stehen hast, dann sieht der Compiler das ja schon wenn er `myfunc()` übersetzt. Die beiden Klassen werden erst zur Laufzeit definiert, aber zur Übersetzungszeit weiss der Compiler halt schon, dass es innerhalb der Funktion die beiden lokalen Namen `Class1` und `Class2` geben wird.

``exec`` ist keine Funktion sondern eine Anweisung -- die Klammern um `code` sind deshalb ein wenig irreführend. Die Anweisung nimmt entweder eine offene Datei, eine Zeichenkette, oder ein vorkompiliertes Code-Objekt entgegen.

Die Ersetzungen, die da gemacht werden, sind IMHO äusserst unschön, weil sehr fehleranfällig. Das ist etwas was man zum Beispiel besser mit einem Namensraum lösen kann. Auf jeden Fall ist `find()` die falsche Methode und die ``if``\s sind im Grunde alle überflüssig, man könnte auch gleich `replace()` anwenden. Wenn das gesuchte nicht vorhanden ist, wird halt auch nichts ersetzt, aber man spart sich die extra Suche vorher, die ja in `replace()` selbst auch noch einmal enthalten ist.

Du bekommst doch den Dateinamen von dem Skript als Argument, warum reichst Du den nicht an `compile()` weiter, statt dort '~/tmp.txt' zu verwenden was sehr verwirrende Stacktraces zu Folge haben kann!?

Und ein "nacktes" ``except`` ist fast nie eine gute Idee. Insbesondere wäre es hier doch schön dem Benutzer auch zu sagen *warum* das Skript nicht kompiliert oder ausgeführt worden konnte. Einfach nur "geht nich" ist bei der Fehlersuche nicht besonders hilfreich.

Ungetestet:

Code: Alles auswählen

    def RunScript(self, filename):
        
        namespace = {
            'DrFilename': self.parent.txtDocument.filename,
            'DrScript': self.parent.DrScript,
            'DrDocument': self.parent.txtDocument,
            'DrPrompt': self.parent.txtPrompt,
            'DrFrame': self.parent
        }
        
        try:
            with open(filename) as script_file:
                exec script_file in namespace
        except Exception, error:
            drScrolledMessageDialog.ShowMessage(
                self.parent,
                'Error running script&#058;',
                'Error: ' + error,
                wx.DefaultPosition,
                (550, 300)
            )
Kann sein, dass ich den `error` an das falsche Argument angehängt habe und man könnte dem Benutzer vielleicht auch noch einen Stacktrace mit in die Fehlerausgabe packen, dann ist der Fehler noch besser zu finden.
Francesco
User
Beiträge: 824
Registriert: Mittwoch 1. Dezember 2004, 12:35
Wohnort: Upper Austria

Vielen Dank nochmals, BlackJack! ;)

Das mit der Error message macht der scrolledmessagedialog, deswegen sieht es hier ein wenig schlampig aus.
(slist = traceback.format_tb(sys.exc_info()[2]) ... )

Hintergrund:
DrPython hat ja Dan Pozmanter geschrieben, aber mangels Zeit schon seit langem aufgegeben. Somit kümmere ich mich mehr oder weniger um das Projekt. Recht viele Anfragen gibt es ohnehin nicht (mehr). Den Code kann und/oder will ich nicht so rechtfertigen. Ich bessere die bugs so aus und versuche, Schwachstellen zu beheben. Eigentlich möchte ich im core gar nicht zuviel ändern (also erweitern), ausser dass die Bedienerfreundlichkeit noch erhöht wird. Ich meine, der hat als Zielgruppe eh keine MS Word User. :twisted:

Das tolle an diesem DrPython ist, dass man es praktisch mit scripts und plugins nahezu ungebrenzt erweitern kann. Und ich habe schon eine recht große Sammlung. Es gibt daneben noch einige events, die vom core geliefert werden und damit sachen bearbeiten kann. zb gibt es 2 events, wo ich bei beginn des saven eines dokument und am Schluss etwas machen kann.
Francesco
User
Beiträge: 824
Registriert: Mittwoch 1. Dezember 2004, 12:35
Wohnort: Upper Austria

BlackJack: Dein Code funktioniert prinzipiell gut, nur jetzt beschwert er sich bei den Scripts dass gewisse module nicht imporiert werden (was vorher nicht war). Kann man dem exec irgendwie mitgeben, dass er gewisse Module "pre"importieren soll, bevor dann exec wirklich ausgeführt wird? Weil in den namescpace packen, das wird ja nicht gehen(?) (habe die scripts dahingehend korrigiert, dass sie das jeweilige modul extra importieren)
Zuletzt geändert von Francesco am Freitag 6. August 2010, 20:17, insgesamt 1-mal geändert.
BlackJack

@Francesco: Module sind Objekte wie andere auch und man kann die selbstverständlich auch mit in den `namespace` stecken. Wenn es überschaubar viele sind, kann man sie von Hand da noch eintragen, ansonsten könnte man auch einfach noch den gesamten Modulnamensraum von dem Modul wo die `RunScript()`-Methode steht in den `namespace` kopieren. Nach der Zuweisung von dem literalen Dictionary mit den `Dr*`-Namen einfach noch ein ``namespace.update(globals())`` sollte das leisten.
Francesco
User
Beiträge: 824
Registriert: Mittwoch 1. Dezember 2004, 12:35
Wohnort: Upper Austria

BlackJack hat geschrieben:@Francesco: Module sind Objekte wie andere auch und man kann die selbstverständlich auch mit in den `namespace` stecken. Wenn es überschaubar viele sind, kann man sie von Hand da noch eintragen, ansonsten könnte man auch einfach noch den gesamten Modulnamensraum von dem Modul wo die `RunScript()`-Methode steht in den `namespace` kopieren. Nach der Zuweisung von dem literalen Dictionary mit den `Dr*`-Namen einfach noch ein ``namespace.update(globals())`` sollte das leisten.
Danke, ist eh schon zu spät, habe sie schon angepasst. ;) Ich glaube, das ist auch der saubere Weg. Die sache mit der class1 ist damit nicht gelöst, aber egal, scripts sind eher für einfache Sachen und Plugins für aufwändigere wie Features im Programm, die auch Preferences brauchen oder Menueinträge mit Shortcuts (die also dauerhaft im code sein sollten und nicht nur bei bedarf, wie bei einem Script, das immer manuell aufgerufen bzw geladen wird.
Antworten