2010-05-26 4 views
5

Ich bin dabei, eine GUI-basierte Anwendung mit Python/Tkinter zu erstellen, die auf dem vorhandenen Python-bdb-Modul aufbaut. In dieser Anwendung möchte ich alle stdout/stderr von der Konsole stummschalten und es auf meine GUI umleiten. Um diesen Zweck zu erreichen, habe ich ein spezielles Tkinter.Text-Objekt geschrieben (Code am Ende des Posts).Segmentierungsfehler beim Umleiten von sys.stdout an das Tkinter.Text-Widget

Die Grundidee ist, dass, wenn etwas in sys.stdout geschrieben wird, es als eine Zeile im "Text" mit der Farbe schwarz angezeigt wird. Wenn etwas in sys.stderr geschrieben wird, erscheint es als eine Zeile im "Text" mit der Farbe rot. Sobald etwas geschrieben wird, scrollt der Text immer nach unten, um die letzte Zeile anzuzeigen.

Ich benutze Python 2.6.1 im Moment. Unter Mac OS X 10.5 scheint das großartig zu funktionieren. Ich hatte keine Probleme damit. Auf RedHat Enterprise Linux 5 erhalte ich jedoch ziemlich zuverlässig einen Segmentierungsfehler während der Ausführung eines Skripts. Der Segmentierungsfehler tritt nicht immer am selben Ort auf, aber er tritt ziemlich oft auf. Wenn ich die Zeilen sys.stdout= und sys.stderr= aus meinem Code auskommentiere, scheinen die Segmentierungsfehler zu verschwinden.

Ich bin mir sicher, dass es andere Wege gibt, auf die ich wahrscheinlich zurückgreifen muss, aber kann irgendjemand irgendetwas sehen, was ich hier eklatant falsch mache, was diese Segmentierungsfehler verursachen könnte? Es macht mich verrückt. Vielen Dank!

PS - Ich weiß, dass die Umleitung von sys.stderr zur GUI vielleicht keine gute Idee ist, aber ich bekomme immer noch Segmentierungsfehler, selbst wenn ich nur sys.stdout und nicht sys.stderr umleite. Ich erkenne auch, dass ich den Text im Moment unbegrenzt wachsen lasse.

class ConsoleText(tk.Text): 
    '''A Tkinter Text widget that provides a scrolling display of console 
    stderr and stdout.''' 

    class IORedirector(object): 
     '''A general class for redirecting I/O to this Text widget.''' 
     def __init__(self,text_area): 
      self.text_area = text_area 

    class StdoutRedirector(IORedirector): 
     '''A class for redirecting stdout to this Text widget.''' 
     def write(self,str): 
      self.text_area.write(str,False) 

    class StderrRedirector(IORedirector): 
     '''A class for redirecting stderr to this Text widget.''' 
     def write(self,str): 
      self.text_area.write(str,True) 

    def __init__(self, master=None, cnf={}, **kw): 
     '''See the __init__ for Tkinter.Text for most of this stuff.''' 

     tk.Text.__init__(self, master, cnf, **kw) 

     self.started = False 
     self.write_lock = threading.Lock() 

     self.tag_configure('STDOUT',background='white',foreground='black') 
     self.tag_configure('STDERR',background='white',foreground='red') 

     self.config(state=tk.DISABLED) 

    def start(self): 

     if self.started: 
      return 

     self.started = True 

     self.original_stdout = sys.stdout 
     self.original_stderr = sys.stderr 

     stdout_redirector = ConsoleText.StdoutRedirector(self) 
     stderr_redirector = ConsoleText.StderrRedirector(self) 

     sys.stdout = stdout_redirector 
     sys.stderr = stderr_redirector 

    def stop(self): 

     if not self.started: 
      return 

     self.started = False 

     sys.stdout = self.original_stdout 
     sys.stderr = self.original_stderr 

    def write(self,val,is_stderr=False): 

     #Fun Fact: The way Tkinter Text objects work is that if they're disabled, 
     #you can't write into them AT ALL (via the GUI or programatically). Since we want them 
     #disabled for the user, we have to set them to NORMAL (a.k.a. ENABLED), write to them, 
     #then set their state back to DISABLED. 

     self.write_lock.acquire() 
     self.config(state=tk.NORMAL) 

     self.insert('end',val,'STDERR' if is_stderr else 'STDOUT') 
     self.see('end') 

     self.config(state=tk.DISABLED) 
     self.write_lock.release() 
+3

nur beiseite, ich würde vorschlagen * nicht * automatisch in allen Fällen nach unten scrollen. Wenn ein Benutzer nach oben geblättert hat, um etwas anzusehen, und dann ein neues Element hinzugefügt wird, wird es ein unzufriedener Benutzer, wenn das, was er betrachtet, aus dem Blickfeld verschwindet. Der Algorithmus, den ich benutze, ist, wenn die letzte Zeile vor der Eingabe von neuem Text sichtbar ist, scrolle ich automatisch. Sonst nicht. –

+0

Guter Anruf. Ich bin mir sicher, dass man bald genug in meiner "To Fix" -Liste aufgetaucht wäre. –

Antwort

3

In Ordnung, so habe ich es geschafft, das Problem aufzuspüren. Ich konnte dieses Problem unter Mac OS X 10.5.8, wo ich den Code ursprünglich entwickelt habe, nie wiederherstellen. Die Segmentierung Fehler scheinen nur auf RedHat Enterprise Linux auftreten 5.

Es stellt sich heraus, dass dieses Stück Code ist der Täter:

def write(self,val,is_stderr=False): 

     #Fun Fact: The way Tkinter Text objects work is that if they're disabled, 
     #you can't write into them AT ALL (via the GUI or programatically). Since we want them 
     #disabled for the user, we have to set them to NORMAL (a.k.a. ENABLED), write to them, 
     #then set their state back to DISABLED. 

     self.write_lock.acquire() 
     self.config(state=tk.NORMAL) 

     self.insert('end',val,'STDERR' if is_stderr else 'STDOUT') 
     self.see('end') 

     self.config(state=tk.DISABLED) 
     self.write_lock.release() 

Ich wünschte, ich für eine Erklärung hatte warum die Segmentierungsfehler sind Vorkommen, aber ich habe festgestellt, dass ständig das Aktivieren und Deaktivieren des Textobjekts der Schuldige ist.

def write(self,val,is_stderr=False): 

     self.write_lock.acquire() 

     self.insert('end',val,'STDERR' if is_stderr else 'STDOUT') 
     self.see('end') 

     self.write_lock.release() 

Meine Segmentation Faults weg, wenn ich die self.config(state=...) Anrufe zu entfernen: Wenn ich das obige Stück Code dies ändern. Der ganze Punkt der self.config(state=...) Aufrufe war es, damit der Benutzer das Textfeld nicht bearbeiten konnte. Wenn sich das Textfeld jedoch in tk.DISABLED befindet, funktionieren Aufrufe von self.insert(...) ebenfalls nicht.

Die Problemumgehung, die ich gefunden habe, besteht darin, das Textfeld aktiviert zu lassen, aber das Textfeld alle Tastatureingaben zu ignorieren (wodurch die Illusion von schreibgeschütztem Verhalten entsteht, wenn der Benutzer versucht, die Tastatur zu verwenden) .Der einfachste Weg, dies zu tun ist, um die __init__ Methode zu ändern wie folgt aussehen (um den Zustand zu tk.NORMAL ändern und die Bindung für <Key> Ereignisse aus):

def __init__(self, master=None, cnf={}, **kw): 
     '''See the __init__ for Tkinter.Text for most of this stuff.''' 

     tk.Text.__init__(self, master, cnf, **kw) 

     self.started = False 
     self.write_lock = threading.Lock() 

     self.tag_configure('STDOUT',background='white',foreground='black') 
     self.tag_configure('STDERR',background='white',foreground='red') 

     self.config(state=tk.NORMAL) 
     self.bind('<Key>',lambda e: 'break') #ignore all key presses 

Hoffnung, dass jeder, der läuft in das gleiche Problem hilft.

+1

Nash: Anstatt Tastatureingaben zu ignorieren, ist es eine Alternative, "Text" aus den Bindtags für das Widget zu entfernen. –

3

Ich nehme an, das ist Teil eines größeren, Thread-Programms.

Anstatt eine Sperre zu verwenden, schreiben Sie Ihren Code in ein Thread-sicheres Warteschlangenobjekt. Dann rufen Sie in Ihrem Haupt-Thread die Warteschlange ab und schreiben in das Text-Widget. Sie können das Polling mit der Ereignisschleife durchführen (im Gegensatz zu einer eigenen Schleife), indem Sie den Polling-Job ausführen, der sich selbst neu plant, um ein paar ms später mit after auszuführen (ein paar hundert ms reichen wahrscheinlich völlig aus).

+0

Leider denke ich, dass ich ein größeres und viel hässlicheres Problem habe, das ich noch debugge, aber ich denke, dass dieses Modell eine sauberere und bessere Weise ist, die Anzeige zu machen. Danke für die Bewertung. –

Verwandte Themen