Jak mogę utworzyć małą powłokę Pythona podobną do IDLE w Tkinter?

9

Próbuję zrobić coś kontrolowanego przez interfejs GUI Pythona.

Jedyną rzeczą jest to, że nie wiem, jak zrobić to całe wejście / wyjście. Chcę tylko móc wpisać dane wejściowe, wykonać polecenie python i podać dane wyjściowe polecenia python. Wiem, że IDLE jest zrobione w Tkinter, więc używa widżetów?

To dosłownie tylko „wejście typu, pokaż wyjście”.

Próbowałem go wyszukać, ale wygląda na to, że większość wyników dotyczy wiersza poleceń, ale nie tego szukam. Jedyne inne pytanie, które było dokładnie takie jak moje, nie dotyczyło również tego, co miałem na myśli. Próbowałem również zlokalizować kod źródłowy dla IDLE, ale nie mogłem znaleźć tego, czego szukałem.

Znalazłem kilka odpowiedzi, które działają w systemie Linux, ale korzystam z systemu Windows 10 ...

Potrzebuję „powłoki”, aby być w Tkinter, ponieważ po jednej stronie ekranu będzie coś innego, co jest podłączone do wyjść poleceń.

Czy ktoś wie, że widżety zostały użyte do stworzenia bardzo prostej powłoki Pythona?

Eleeza the Wizard Wizard
źródło
interesujące mogą być stackoverflow.com/questions/38977525/… .
jasonharper,
To pytanie wygląda na następstwo innego, które zostało usunięte, to nie miało dla mnie sensu bez kontekstu z usuniętego pytania ... cóż, przynajmniej wiem teraz, że jest to możliwe - i że nie jestem tylko jeden próbuje coś zrobić ... znowu
Eleeza the Wizard Wizard
1
Bezczynność jest napisana w pythonie, używając tkinter ... czytaj kod źródłowy .
Reblochon Masque,

Odpowiedzi:

13

Prosta powłoka Pythona / Terminal / Wiersz polecenia


  • ********************* Jest to dosłownie po prostu „ ” rzecz. ************************type inputshow output

import os
from tkinter import *
from subprocess import *


class PythonShell:

    def __init__(self):
        self.master = Tk()

        self.mem_cache = open("idle.txt", "w+")
        self.body = None
        self.entry = None
        self.button = None
        self.entry_content = None

    @staticmethod
    def welcome_note():
        """
        To show welcome note on tkinter window
        :return:
        """
        Label(text="Welcome To My Python Program [Version 1.0]", font='Arial 12', background="#272626",
              foreground="white").pack()

        Label(text=">> Insert Python Commands <<", font='Arial 12', background="#272626",
              foreground="white").pack()

    def get_text(self):
        """
        This method will perform following operations;
        1- Get text from body
        2- Implies python compilation (treat text as command)
        3- Set Output in Output-Entry

        :return: get and set text in body of text box
        """
        content = self.body.get(1.0, "end-1c")
        out_put = self.run_commands(content)
        self.entry_content.set(out_put)

    def store_commands(self, command=None):

        try:
            self.mem_cache.write(command + ';')
            self.mem_cache.close()

        except Exception as e:
            print(e)

    def get_stored_commands(self):
        try:
            with open("idle.txt", "r") as self.mem_cache:
                self.mem_cache.seek(0)
                val = self.mem_cache.read()
                self.mem_cache.close()
                return val

        except Exception as e:
            print(e)

    @staticmethod
    def check_if_file_empty():
        size = os.stat("idle.txt").st_size

        if size != 0:
            return True
        else:
            return False

    def run_commands(self, command):
        """

        This method would return output of every command place in text box
        :param command: python command from text box
        :return: output of command
        """

        print("Running command: {}".format(command))
        value = None
        new_line_char = command.find('\n')
        semi_colons_char = command.find(';')
        double_quote = command.find('"')

        try:
            if new_line_char != -1:

                if semi_colons_char != -1 & double_quote == -1:

                    new_cmd = command.replace("\n", "")
                    cmd_value = '"' + new_cmd + '"'
                    self.store_commands(command)

                    value = check_output("python -c " + cmd_value, shell=True).decode()
                elif semi_colons_char == -1 & double_quote == -1:

                    new_cmd = command.replace("\n", ";")
                    cmd_value = '"' + new_cmd + '"'
                    self.store_commands(command)
                    value = check_output("python -c " + cmd_value, shell=True).decode()

                elif double_quote != -1:

                    cmd_1 = command.replace('"', "'")
                    new_cmd = cmd_1.replace('\n', ';')

                    cmd_value = '"' + new_cmd + '"'
                    self.store_commands(command)

                    value = check_output("python -c " + cmd_value, shell=True).decode()

                elif self.body.compare("end-1c", "==", "1.0"):
                    self.entry_content.set("the widget is empty")

            elif self.body.compare("end-1c", "==", "1.0"):
                value = "The widget is empty. Please Enter Something."

            else:
                variable_analyzer = command.find('=')
                file_size = PythonShell.check_if_file_empty()

                if file_size:
                    new_cmd = command.replace('"', "'")
                    cmd_value = '"' + new_cmd + '"'
                    stored_value = self.get_stored_commands()
                    cmd = stored_value + cmd_value
                    cmd.replace('"', '')

                    value = check_output("python -c " + cmd, shell=True).decode()
                elif variable_analyzer != -1:
                    new_cmd = command.replace('"', "'")
                    cmd_value = '"' + new_cmd + '"'
                    self.store_commands(cmd_value)

                    value = 'Waiting for input...'
                    pass
                else:
                    new_cmd = command.replace('"', "'")
                    cmd_value = '"' + new_cmd + '"'
                    value = check_output("python -c " + cmd_value, shell=True).decode()

        except Exception as ex:
            print('>>>', ex)
            self.entry_content.set('Invalid Command. Try again!!!')

        print('>>', value)
        # To Clear Text body After Button Click
        # self.body.delete('1.0', END)

        return value

    def start_terminal(self):
        """
        Initiate tkinter session to place and run commands
        :return:
        """
        self.master.propagate(0)
        self.master.geometry('750x350')
        self.master.title('Python IDLE')
        self.master.configure(background='#272626')

        terminal.welcome_note()

        self.body = Text(self.master, height='10', width='75', font='Consolas 12', background="#272626",
                         foreground="white",
                         insertbackground='white')
        # self.body.propagate(0)
        self.body.pack(expand=True)

        Label(text=">> Command Output <<", font='Arial 12', background="#272626",
              foreground="white").pack()

        self.entry_content = StringVar()
        self.entry = Entry(self.master, textvariable=self.entry_content, width=50, font='Consolas 16',
                           background="white",
                           foreground="black")
        self.entry.pack()
        # self.entry.propagate(0)

        self.button = Button(self.master, text="Run Command", command=self.get_text, background="white",
                             foreground="black",
                             font='Helvetica 12').pack()

        self.master.mainloop()


if __name__ == '__main__':
    terminal = PythonShell()
    terminal.start_terminal()

Powyższy skrypt Pythona ma następującą hierarchię, jak podano;

    |import ...      
    |class PythonShell:
        |def __init__(self):...

        @staticmethod
        |def welcome_note():...
        |def get_text(self):...
        |def store_commands(self, commmand):...
        |def get_stored_commands(self):...

        @staticmethod
        |def check_if_file_empty():
        |def run_commands(self, command):...
        |def start_terminal(self):...

    |if __name__ == '__main__':...

Przepływ pracy:

Podstawowy przepływ pracy dla powyższego kodu podano w następujący sposób;

  • def welcome_note():... Zawiera etykietę, która będzie wyświetlana poza treścią tekstu.

  • def get_text(self):...Wykonuje dwie operacje; ** Pobierz tekst z tekstu ** i ** Ustaw dane wyjściowe w polu wprowadzania **.

  • def store_commands(self, command):... Służy do przechowywania zmiennych w pliku.

  • def get_stored_commands(self):... Pobierz zmienną zapisaną w pliku.

  • def check_if_file_empty():... Sprawdź rozmiar pliku.

  • def run_commands(self, command):...Ta metoda działa jako kompilator Pythona, który przyjmuje polecenia, wykonuje przetwarzanie i generuje dane wyjściowe dla podanego polecenia. Aby uruchamiać polecenia, polecam ich używać, subprocess-moduleponieważ zapewnia ono bardziej zaawansowane funkcje do odradzania nowych procesów i pobierania ich wyników; Aby uruchomić polecenia systemu Windows za pomocą Pythona, zawiera różne wbudowane biblioteki, takie jak;

    1. os ( szczegółowo ), 2. podproces ( szczegółowo ) itp.

    Aby sprawdzić, który jest lepszy w użyciu, odwiedź odnośnik: moduł podprocesowy jest lepszy niż moduł os .

  • def start_terminal(self):...Ta metoda obejmuje po prostu funkcję inicjowania tkinterokna sesji i pokazania podstawowego układu okna wprowadzania i wyświetlania.

    Możesz dalej modyfikować i optymalizować ten kod zgodnie z własnymi wymaganiami.


Obejście:

Ta prosta tkinter GUI based python shellwykonuje prostą funkcjonalność jako wiersz polecenia systemu Windows. Aby uruchamiać polecenia python directlyw wierszu polecenia bez przechodzenia do terminala python, wykonujemy proste jako;

python -c "print('Hey Eleeza!!!')"

Jego wynik byłby prosty jak;

Hey Eleeza!!!

Podobnie, aby uruchomić więcej niż jedną linię bezpośrednio na raz, jak podano;

python -c "import platform;sys_info=platform.uname();print(sys_info)"

Jego wyniki byłyby takie;

My System Info: uname_result(system='Windows', node='DESKTOP-J75UTG5', release='10', version='10.0.18362', machine='AMD64', processor='Intel64 Family 6 Model 142 Stepping 10, GenuineIntel')

Więc użyć tego tkinter python shell;

  • Albo możesz umieścić polecenie jako;

    import platform
    value=platform.uname()
    print('Value:', value)
  • lub w ten sposób;

    import platform;value=platform.uname();
    print('Value:', value)
  • lub po prostu polecenie wbudowane jako

    import platform;value=platform.uname();print('Value:', value)

Otrzymasz ten sam wynik.

Muhammad Usman
źródło
1
To wejście / wyjście jest idealne, dziękuję bardzo! Tylko jedna rzecz, jeśli przypiszę zmienną, uruchom polecenie, a następnie wyczyść i spróbuj wydrukować zmienną, to nie robi tego, więc w jaki sposób mam sprawić, aby polecenie działało w prawdziwym pythonie (jeśli nie jest robiąc to)?
Eleeza the Wizard Wizard
2
@Eleeza, przede wszystkim twoje pytanie nie dotyczyło tego rodzaju wymagań. Ponieważ jest to bardzo proste input, output shell. Działa jak podstawowy python kernel. Działa tylko na których jest umieszczony w tekście . Nie ustawiłem żadnego bufora ani pamięci podręcznej do przechowywania historii zmiennych. Pozwól, że sprawdzę najpierw ten wymóg w tym IDLE!
Muhammad Usman,
1
Miałem zamiar stworzyć grę, w której cały świat jest zaprogramowany w głównym kodzie, a terminal służy do jego eksploracji i interakcji z nim. Myślałem, że będzie to po prostu wykonanie kodu przed wyświetleniem danych wyjściowych, ale muszę to sprawdzić.
Eleeza the Wizard Wizard
2
Szczegółowo wyjaśnione: Tworzę coś w rodzaju „świata” w głównym programie gry, w którym ludzie, miejsca itp. Są obiektami w Pythonie. Gracz musi poruszać się po świecie przez terminal GUI, używać poleceń Pythona, to gra o nauce języka Python poprzez eksplorację. Kod jest w rzeczywistości modyfikowany w rzeczywistości (ale resetuje się później).
Eleeza the Wizard Wizard
8
@Eleeza, oto najlepsze repozytorium github, które może dać ci pełną ścieżkę, na której jesteś; Odwiedź odnośnik: github.com/codecombat/codecombat . Oto kilka innych odniesień, na które musisz również spojrzeć; github.com/replit/play , github.com/jatinmandav/Gaming-in-Python , github.com/PacktPublishing/-Learn-Python-Programming-with-Games , github.com/… , github.com/pyland/pyland , Oto kolejne odniesienie również github.com/CharlesPikachu/Games
Muhammad Usman,
4

Jest to prosta powłoka używana głównie exec()do wykonywania instrukcji python i subprocess.Popen()wykonywania poleceń zewnętrznych:

import tkinter as tk
import sys, io
import subprocess as subp
from contextlib import redirect_stdout

class Shell(tk.Text):
  def __init__(self, parent, **kwargs):
    tk.Text.__init__(self, parent, **kwargs)
    self.bind('<Key>', self.on_key) # setup handler to process pressed keys
    self.cmd = None        # hold the last command issued
    self.show_prompt()

  # to append given text at the end of Text box
  def insert_text(self, txt='', end='\n'):
    self.insert(tk.END, txt+end)
    self.see(tk.END) # make sure it is visible

  def show_prompt(self):
    self.insert_text('>> ', end='')
    self.mark_set(tk.INSERT, tk.END) # make sure the input cursor is at the end
    self.cursor = self.index(tk.INSERT) # save the input position

  # handler to process keyboard input
  def on_key(self, event):
    #print(event)
    if event.keysym == 'Up':
      if self.cmd:
        # show the last command
        self.delete(self.cursor, tk.END)
        self.insert(self.cursor, self.cmd)
      return "break" # disable the default handling of up key
    if event.keysym == 'Down':
      return "break" # disable the default handling of down key
    if event.keysym in ('Left', 'BackSpace'):
      current = self.index(tk.INSERT) # get the current position of the input cursor
      if self.compare(current, '==', self.cursor):
        # if input cursor is at the beginning of input (after the prompt), do nothing
        return "break"
    if event.keysym == 'Return':
      # extract the command input
      cmd = self.get(self.cursor, tk.END).strip()
      self.insert_text() # advance to next line
      if cmd.startswith('`'):
        # it is an external command
        self.system(cmd)
      else:
        # it is python statement
        self.execute(cmd)
      self.show_prompt()
      return "break" # disable the default handling of Enter key
    if event.keysym == 'Escape':
      self.master.destroy() # quit the shell

  # function to handle python statement input
  def execute(self, cmd):
    self.cmd = cmd  # save the command
    # use redirect_stdout() to capture the output of exec() to a string
    f = io.StringIO()
    with redirect_stdout(f):
      try:
        exec(self.cmd, globals())
      except Exception as e:
        print(e)
    # then append the output of exec() in the Text box
    self.insert_text(f.getvalue(), end='')

  # function to handle external command input
  def system(self, cmd):
    self.cmd = cmd  # save the command
    try:
      # extract the actual command
      cmd = cmd[cmd.index('`')+1:cmd.rindex('`')]
      proc = subp.Popen(cmd, stdout=subp.PIPE, stderr=subp.PIPE, text=True)
      stdout, stderr = proc.communicate(5) # get the command output
      # append the command output to Text box
      self.insert_text(stdout)
    except Exception as e:
      self.insert_text(str(e))

root = tk.Tk()
root.title('Simple Python Shell')

shell = Shell(root, width=100, height=50, font=('Consolas', 10))
shell.pack(fill=tk.BOTH, expand=1)
shell.focus_set()

root.mainloop()

Wystarczy wpisać normalną instrukcję python:

>> x = 1
>> print(x)
1

Lub wprowadź polecenie powłoki:

>> `cmd /c date /t`
2019-12-09

Możesz także użyć Upklawisza do przywołania ostatniego polecenia.

Pamiętaj, że jeśli wykonasz polecenie systemowe wymagające wprowadzenia danych przez użytkownika, powłoka zostanie zamrożona na 5 sekund (użyto limitu czasu communicate()).

Możesz zmodyfikować on_key()funkcję w zależności od potrzeb.

Przypominamy również, że używanie exec()nie jest dobrą praktyką.

acw1668
źródło
Czy możesz wyjaśnić kod, proszę? Jestem trochę początkującym w tym wszystkim ... i nie wiem, czy rozumiem poprawnie
Eleeza the Wizard Wizard
1
Dodałem komentarze do mojego kodu, mam nadzieję, że to pomoże.
acw1668,
3

Zaimplementowałem powłokę Pythona, code.InteractiveConsoleaby wykonać polecenia dla projektu. Poniżej znajduje się uproszczona wersja, choć wciąż dość długa, ponieważ napisałem powiązania dla klawiszy specjalnych (takich jak Return, Tab ...), które zachowują się jak w konsoli Pythona. Można dodać więcej funkcji, takich jak autouzupełnianie za pomocą jedi i podświetlanie składni za pomocą pygmentów.

Główną ideą jest to, że używam push()metody code.InteractiveConsoledo wykonywania poleceń. Ta metoda zwraca, Truejeśli jest to polecenie częściowe, np. def test(x):I używam tej informacji zwrotnej, aby wstawić ...monit, w przeciwnym razie dane wyjściowe zostaną wyświetlone i zostanie wyświetlone nowe >>>zapytanie. Przechwytywam dane wyjściowe za pomocą contextlib.redirect_stdout.

Jest też dużo kodu obejmującego znaki i porównywanie indeksów, ponieważ uniemożliwiam użytkownikowi wstawianie tekstu do wcześniej wykonanych poleceń. Chodzi o to, że utworzyłem znak „wejście”, który mówi mi, gdzie jest początek aktywnego monitu, i self.compare('insert', '<', 'input')mogę wiedzieć, kiedy użytkownik próbuje wstawić tekst powyżej aktywnego monitu.

import tkinter as tk
import sys
import re
from code import InteractiveConsole
from contextlib import redirect_stderr, redirect_stdout
from io import StringIO


class History(list):
    def __getitem__(self, index):
        try:
            return list.__getitem__(self, index)
        except IndexError:
            return


class TextConsole(tk.Text):
    def __init__(self, master, **kw):
        kw.setdefault('width', 50)
        kw.setdefault('wrap', 'word')
        kw.setdefault('prompt1', '>>> ')
        kw.setdefault('prompt2', '... ')
        banner = kw.pop('banner', 'Python %s\n' % sys.version)
        self._prompt1 = kw.pop('prompt1')
        self._prompt2 = kw.pop('prompt2')
        tk.Text.__init__(self, master, **kw)
        # --- history
        self.history = History()
        self._hist_item = 0
        self._hist_match = ''

        # --- initialization
        self._console = InteractiveConsole() # python console to execute commands
        self.insert('end', banner, 'banner')
        self.prompt()
        self.mark_set('input', 'insert')
        self.mark_gravity('input', 'left')

        # --- bindings
        self.bind('<Control-Return>', self.on_ctrl_return)
        self.bind('<Shift-Return>', self.on_shift_return)
        self.bind('<KeyPress>', self.on_key_press)
        self.bind('<KeyRelease>', self.on_key_release)
        self.bind('<Tab>', self.on_tab)
        self.bind('<Down>', self.on_down)
        self.bind('<Up>', self.on_up)
        self.bind('<Return>', self.on_return)
        self.bind('<BackSpace>', self.on_backspace)
        self.bind('<Control-c>', self.on_ctrl_c)
        self.bind('<<Paste>>', self.on_paste)

    def on_ctrl_c(self, event):
        """Copy selected code, removing prompts first"""
        sel = self.tag_ranges('sel')
        if sel:
            txt = self.get('sel.first', 'sel.last').splitlines()
            lines = []
            for i, line in enumerate(txt):
                if line.startswith(self._prompt1):
                    lines.append(line[len(self._prompt1):])
                elif line.startswith(self._prompt2):
                    lines.append(line[len(self._prompt2):])
                else:
                    lines.append(line)
            self.clipboard_clear()
            self.clipboard_append('\n'.join(lines))
        return 'break'

    def on_paste(self, event):
        """Paste commands"""
        if self.compare('insert', '<', 'input'):
            return "break"
        sel = self.tag_ranges('sel')
        if sel:
            self.delete('sel.first', 'sel.last')
        txt = self.clipboard_get()
        self.insert("insert", txt)
        self.insert_cmd(self.get("input", "end"))
        return 'break'

    def prompt(self, result=False):
        """Insert a prompt"""
        if result:
            self.insert('end', self._prompt2, 'prompt')
        else:
            self.insert('end', self._prompt1, 'prompt')
        self.mark_set('input', 'end-1c')

    def on_key_press(self, event):
        """Prevent text insertion in command history"""
        if self.compare('insert', '<', 'input') and event.keysym not in ['Left', 'Right']:
            self._hist_item = len(self.history)
            self.mark_set('insert', 'input lineend')
            if not event.char.isalnum():
                return 'break'

    def on_key_release(self, event):
        """Reset history scrolling"""
        if self.compare('insert', '<', 'input') and event.keysym not in ['Left', 'Right']:
            self._hist_item = len(self.history)
            return 'break'

    def on_up(self, event):
        """Handle up arrow key press"""
        if self.compare('insert', '<', 'input'):
            self.mark_set('insert', 'end')
            return 'break'
        elif self.index('input linestart') == self.index('insert linestart'):
            # navigate history
            line = self.get('input', 'insert')
            self._hist_match = line
            hist_item = self._hist_item
            self._hist_item -= 1
            item = self.history[self._hist_item]
            while self._hist_item >= 0 and not item.startswith(line):
                self._hist_item -= 1
                item = self.history[self._hist_item]
            if self._hist_item >= 0:
                index = self.index('insert')
                self.insert_cmd(item)
                self.mark_set('insert', index)
            else:
                self._hist_item = hist_item
            return 'break'

    def on_down(self, event):
        """Handle down arrow key press"""
        if self.compare('insert', '<', 'input'):
            self.mark_set('insert', 'end')
            return 'break'
        elif self.compare('insert lineend', '==', 'end-1c'):
            # navigate history
            line = self._hist_match
            self._hist_item += 1
            item = self.history[self._hist_item]
            while item is not None and not item.startswith(line):
                self._hist_item += 1
                item = self.history[self._hist_item]
            if item is not None:
                self.insert_cmd(item)
                self.mark_set('insert', 'input+%ic' % len(self._hist_match))
            else:
                self._hist_item = len(self.history)
                self.delete('input', 'end')
                self.insert('insert', line)
            return 'break'

    def on_tab(self, event):
        """Handle tab key press"""
        if self.compare('insert', '<', 'input'):
            self.mark_set('insert', 'input lineend')
            return "break"
        # indent code
        sel = self.tag_ranges('sel')
        if sel:
            start = str(self.index('sel.first'))
            end = str(self.index('sel.last'))
            start_line = int(start.split('.')[0])
            end_line = int(end.split('.')[0]) + 1
            for line in range(start_line, end_line):
                self.insert('%i.0' % line, '    ')
        else:
            txt = self.get('insert-1c')
            if not txt.isalnum() and txt != '.':
                self.insert('insert', '    ')
        return "break"

    def on_shift_return(self, event):
        """Handle Shift+Return key press"""
        if self.compare('insert', '<', 'input'):
            self.mark_set('insert', 'input lineend')
            return 'break'
        else: # execute commands
            self.mark_set('insert', 'end')
            self.insert('insert', '\n')
            self.insert('insert', self._prompt2, 'prompt')
            self.eval_current(True)

    def on_return(self, event=None):
        """Handle Return key press"""
        if self.compare('insert', '<', 'input'):
            self.mark_set('insert', 'input lineend')
            return 'break'
        else:
            self.eval_current(True)
            self.see('end')
        return 'break'

    def on_ctrl_return(self, event=None):
        """Handle Ctrl+Return key press"""
        self.insert('insert', '\n' + self._prompt2, 'prompt')
        return 'break'

    def on_backspace(self, event):
        """Handle delete key press"""
        if self.compare('insert', '<=', 'input'):
            self.mark_set('insert', 'input lineend')
            return 'break'
        sel = self.tag_ranges('sel')
        if sel:
            self.delete('sel.first', 'sel.last')
        else:
            linestart = self.get('insert linestart', 'insert')
            if re.search(r'    $', linestart):
                self.delete('insert-4c', 'insert')
            else:
                self.delete('insert-1c')
        return 'break'

    def insert_cmd(self, cmd):
        """Insert lines of code, adding prompts"""
        input_index = self.index('input')
        self.delete('input', 'end')
        lines = cmd.splitlines()
        if lines:
            indent = len(re.search(r'^( )*', lines[0]).group())
            self.insert('insert', lines[0][indent:])
            for line in lines[1:]:
                line = line[indent:]
                self.insert('insert', '\n')
                self.prompt(True)
                self.insert('insert', line)
                self.mark_set('input', input_index)
        self.see('end')

    def eval_current(self, auto_indent=False):
        """Evaluate code"""
        index = self.index('input')
        lines = self.get('input', 'insert lineend').splitlines() # commands to execute
        self.mark_set('insert', 'insert lineend')
        if lines:  # there is code to execute
            # remove prompts
            lines = [lines[0].rstrip()] + [line[len(self._prompt2):].rstrip() for line in lines[1:]]
            for i, l in enumerate(lines):
                if l.endswith('?'):
                    lines[i] = 'help(%s)' % l[:-1]
            cmds = '\n'.join(lines)
            self.insert('insert', '\n')
            out = StringIO()  # command output
            err = StringIO()  # command error traceback
            with redirect_stderr(err):     # redirect error traceback to err
                with redirect_stdout(out): # redirect command output
                    # execute commands in interactive console
                    res = self._console.push(cmds)
                    # if res is True, this is a partial command, e.g. 'def test():' and we need to wait for the rest of the code
            errors = err.getvalue()
            if errors:  # there were errors during the execution
                self.insert('end', errors)  # display the traceback
                self.mark_set('input', 'end')
                self.see('end')
                self.prompt() # insert new prompt
            else:
                output = out.getvalue()  # get output
                if output:
                    self.insert('end', output, 'output')
                self.mark_set('input', 'end')
                self.see('end')
                if not res and self.compare('insert linestart', '>', 'insert'):
                    self.insert('insert', '\n')
                self.prompt(res)
                if auto_indent and lines:
                    # insert indentation similar to previous lines
                    indent = re.search(r'^( )*', lines[-1]).group()
                    line = lines[-1].strip()
                    if line and line[-1] == ':':
                        indent = indent + '    '
                    self.insert('insert', indent)
                self.see('end')
                if res:
                    self.mark_set('input', index)
                    self._console.resetbuffer()  # clear buffer since the whole command will be retrieved from the text widget
                elif lines:
                    self.history.append(lines)  # add commands to history
                    self._hist_item = len(self.history)
            out.close()
            err.close()
        else:
            self.insert('insert', '\n')
            self.prompt()


if __name__ == '__main__':
    root = tk.Tk()
    console = TextConsole(root)
    console.pack(fill='both', expand=True)
    root.mainloop()
j_4321
źródło