2010-05-26 10 views
5

Sono in procinto di creare un'applicazione basata su GUI con Python/Tkinter che si basa sul modulo bdb Python esistente. In questa applicazione, voglio silenziare tutto stdout/stderr dalla console e reindirizzare la mia GUI. Per raggiungere questo scopo, ho scritto un oggetto specializzato Tkinter.Text (codice alla fine del post).Errore di segmentazione durante il reindirizzamento di sys.stdout a Tkinter.Text widget

L'idea di base è che quando qualcosa viene scritto su sys.stdout, viene visualizzato come una linea nel "Testo" con il colore nero. Se qualcosa viene scritto su sys.stderr, viene visualizzato come una linea nel "Testo" con il colore rosso. Non appena viene scritto qualcosa, il testo scorre sempre verso il basso per visualizzare la riga più recente.

Attualmente sto usando Python 2.6.1. Su Mac OS X 10.5, sembra funzionare alla grande. Ho avuto zero problemi con esso. Su RedHat Enterprise Linux 5, tuttavia, ho ottenuto un errore di segmentazione in modo affidabile durante l'esecuzione di uno script. L'errore di segmentazione non si verifica sempre nello stesso punto, ma si verifica quasi sempre. Se commento le linee sys.stdout= e sys.stderr= dal mio codice, gli errori di segmentazione sembrano andare via.

Sono sicuro che ci sono altri modi intorno a questo che probabilmente dovrò ricorrere a, ma qualcuno può vedere qualcosa che sto facendo clamorosamente sbagliato qui che potrebbe causare questi errori di segmentazione? Mi sta facendo impazzire. Grazie!

PS - Mi rendo conto che il reindirizzamento di sys.stderr alla GUI potrebbe non essere una buona idea, ma ottengo ancora errori di segmentazione anche quando reindirizzo solo sys.stdout e non sys.stderr. Mi rendo anche conto che sto permettendo che il testo cresca indefinitamente al momento.

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

solo a parte, vorrei suggerire * not * automaticamente lo scorrimento verso il basso in tutti i casi. Se un utente ha fatto scorrere verso l'alto per guardare qualcosa e poi viene aggiunto un nuovo elemento, sarà un utente infelice quando ciò che sta guardando si sposta fuori dalla vista. L'algoritmo che uso è, se l'ultima riga è visibile prima di inserire il nuovo testo, si scorre automaticamente. Altrimenti no. –

+0

Buona chiamata. Sono sicuro che uno sarebbe apparso nella mia lista "To Fix" abbastanza presto. –

risposta

3

OK, quindi sono riuscito a rintracciare il problema. Non sono mai stato in grado di ricreare questo problema su Mac OS X 10.5.8, dove inizialmente ho sviluppato il codice. I difetti di segmentazione solo sembrano verificarsi su RedHat Enterprise Linux 5.

Si scopre che questo pezzo di codice è il colpevole:

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() 

Vorrei avere una spiegazione per il motivo per cui i difetti di segmentazione sono si verificano, ma ho scoperto che l'attivazione e la disattivazione costante dell'oggetto Testo è il colpevole. Se cambio il pezzo di sopra del codice a questo:

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() 

miei difetti di segmentazione andare via quando rimuovo i self.config(state=...) chiamate. L'intero punto delle chiamate self.config(state=...) era di farlo in modo che l'utente non potesse modificare il campo di testo. Quando il campo Testo è nello stato tk.DISABLED, le chiamate a self.insert(...) non funzionano neanche.

La soluzione alternativa che ho trovato è quella di lasciare il campo Testo abilitato, ma fare in modo che il campo Testo ignori tutti gli input da tastiera (dando così l'illusione di comportamento di sola lettura se l'utente tenta di usare la tastiera) .Il modo più semplice per farlo è quello di cambiare il metodo di __init__ per assomigliare a questo (cambiare lo stato di tk.NORMAL e cambiare l'associazione per <Key> eventi):

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 

Speranza che aiuta chi gestisce lo stesso problema.

+1

Nash: Invece di ignorare i tasti premuti, un'alternativa è rimuovere "Testo" dalle bindtag per il widget. –

3

Presumo che questo sia parte di un programma con thread più grande.

Invece di utilizzare un blocco, fare scrivere il codice su un oggetto coda sicuro per i thread. Quindi, nel thread principale, si esegue il polling della coda e si scrive nel widget di testo. È possibile eseguire il polling utilizzando il ciclo degli eventi (anziché scrivere il proprio loop) eseguendo il lavoro di polling che riprogramma da solo per eseguire alcuni ms in un secondo momento utilizzando (un paio di centinaia di ms è probabilmente sufficiente).

+0

Purtroppo penso di avere un problema più grande e molto più brutto che sto ancora facendo il debug, ma penso che questo modello sia un modo più pulito e migliore di fare il display. Grazie per il feedback. –

Problemi correlati