2010-05-26 10 views
5

Jestem w trakcie budowania aplikacji opartej na interfejsie graficznym z Pythonem/Tkinter, która opiera się na istniejącym module bdb Pythona. W tej aplikacji chcę wyciszyć wszystkie stdout/stderr z konsoli i przekierować ją do mojego GUI. Aby osiągnąć ten cel, napisałem specjalistyczny obiekt Tkinter.Text (kod na końcu postu).Błąd segmentacji podczas przekierowywania sys.stdout do widżetu Tkinter.Text

Podstawową ideą jest, że gdy coś jest napisane do sys.stdout, pojawia się jako linia w "Tekst" z kolorem czarnym. Jeśli coś jest napisane do sys.stderr, pojawia się jako linia w "Text" z kolorem czerwonym. Gdy tylko coś zostanie napisane, tekst zawsze przewija się w dół, aby wyświetlić najnowszą linię.

Używam Python 2.6.1 w tej chwili. W systemie Mac OS X 10.5 wygląda na to, że działa świetnie. Nie miałem z tym problemów. Jednak w RedHat Enterprise Linux 5 całkiem niezawodnie otrzymuję błąd segmentacji podczas uruchamiania skryptu. Błąd segmentacji nie zawsze występuje w tym samym miejscu, ale prawie zawsze występuje. Jeśli skomentuję linie sys.stdout= i sys.stderr= z mojego kodu, błędy segmentacji wydają się znikać.

Jestem pewien, że są inne sposoby obejścia tego, do czego prawdopodobnie będę musiał się odwoływać, ale czy ktoś może zobaczyć coś, co robię rażąco nie tak tutaj, co może być przyczyną tych błędów segmentacji? To doprowadza mnie do szału. Dzięki!

PS - Rozumiem, że przekierowanie sys.stderr do GUI może nie być dobrym pomysłem, ale nadal dostaję błędy segmentacji, nawet gdy przekierowuję tylko sys.stdout, a nie sys.stderr. Zdaję sobie również sprawę, że pozwalam, aby tekst narastał w nieskończoność.

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

tak na marginesie sugerowałbym * nie * automatyczne przewijanie we wszystkich przypadkach. Jeśli użytkownik przewinął do góry, aby na coś spojrzeć, a następnie dodano nowy element, będzie nieszczęśliwym użytkownikiem, gdy to, na co patrzy, znika z widoku. Używam algorytmu, jeśli ostatnia linia jest widoczna przed wprowadzeniem nowego tekstu, automatycznie przewijam. W przeciwnym razie nie. –

+0

Dobra rozmowa. Jestem pewien, że wkrótce pojawi się na mojej liście "Naprawić". –

Odpowiedz

3

W porządku, udało mi się więc znaleźć przyczynę problemu. Nigdy nie udało mi się odtworzyć tego problemu w systemie Mac OS X 10.5.8, w którym pierwotnie opracowałem kod. Do naruszenie ochrony pamięci tylko wydają się występować na RedHat Enterprise Linux 5.

Okazuje się, że ten fragment kodu jest winowajcą:

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

Chciałbym mieć wytłumaczenie dla dlaczego błędy segmentacji występujące, ale stwierdziłem, że sprawcą jest stałe włączanie i wyłączanie obiektu Text. Jeśli zmienić powyższy fragment kodu do tego:

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

Moi naruszenie ochrony pamięci odejść kiedy usunąć połączenia self.config(state=...). Cały punkt wywołania self.config(state=...) polegał na tym, aby użytkownik nie mógł edytować pola tekstowego. Gdy pole tekstowe znajduje się w stanie tk.DISABLED, połączenia z self.insert(...) również nie działają.

Rozwiązaniem, które zaproponowałem, jest pozostawienie pola Tekst włączone, ale powoduje, że pole Tekst ignoruje wszystkie dane wprowadzane z klawiatury (co daje iluzję zachowania tylko do odczytu, jeśli użytkownik próbuje użyć klawiatury) .Najprostszym sposobem, aby to zrobić, aby zmienić metodę __init__ wyglądać następująco (zmienia stan na tk.NORMAL i zmienić wiązanie na <Key> zdarzeń):

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 

nadzieję, że pomoże każdemu, kto działa na ten sam problem.

+1

Nash: Zamiast ignorować naciśnięcia klawiszy, alternatywą jest usunięcie "Tekstu" z bindtags dla widgetu. –

3

Zakładam, że jest to część większego programu z wątkami.

Zamiast używać blokady, należy napisać kod do obiektu kolejki bezpiecznej dla wątków. Następnie w głównym wątku odpytujesz kolejkę i zapisujesz w widgecie tekstowym. Możesz przeprowadzić polling za pomocą pętli zdarzeń (w przeciwieństwie do zapisu własnej pętli), uruchamiając zadanie odpytywania, które zmienia się samoczynnie, aby uruchomić kilka ms później przy użyciu po (kilkaset ms to prawdopodobnie wystarczająca ilość).

+0

Niestety, wydaje mi się, że mam większy i dużo brzydszy problem, że nadal debuguję, ale uważam, że ten model jest czystszy i lepszy sposób wyświetlania. Dziękuję za zwrotną informację. –

Powiązane problemy