Як я можу створити невелику IDLE-подібну Python Shell у Tkinter?


9

Я намагаюся зробити те, що контролюється графічним інтерфейсом Python Shell.

Єдине, я не знаю, як зробити всю цю річ вводу / виводу. Я просто хочу мати можливість ввести вхід, виконати команду python і дати вихід команди python. Я знаю, що IDLE створений у Tkinter, тому він використовує віджети?

Це буквально просто "тип введення, показ результату".

Я намагався шукати його, але, схоже, більшість результатів пов'язані з командним рядком, який я не шукаю. Єдине інше питання, яке було точно таким, як у мене, не було того, що я мав на увазі. Я також спробував знайти вихідний код для IDLE, але не зміг знайти те, що шукав.

Я знайшов відповіді, які працюють для Linux, але я в Windows 10 ...

Мені потрібна "оболонка", щоб вона була в Tkinter, тому що на одній стороні екрану буде щось інше, що підключено до командних виходів.

Хтось знає віджети, які використовуються для створення дуже простої оболонки Python?



Це питання схоже на подальше спостереження за іншим, яке видалено, воно ніби не мало сенсу для мене без контексту видаленого питання ... ну, принаймні, я знаю, що зараз це можливо - і що я не є тільки один намагається щось зробити ... знову
Елеза Чарівник персонажів

1
Простір записується в python, використовуючи tkinter ... читайте вихідний код .
Маска

Відповіді:


13

Прості оболонки Python / Terminal / Command-prompt


  • ********************* Це буквально просто " type input, show 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()

Наведений вище сценарій python має таку ієрархію, як дано;

    |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__':...

Робочий процес:

Основний робочий процес для наведеного вище коду наведено наступним чином;

  • def welcome_note():... Включає мітку, яка відображатиметься поза текстовим текстом.

  • def get_text(self):...Виконує дві операції; ** Отримати текст із тексту тексту ** та ** Встановити вихід у поле вводу **.

  • def store_commands(self, command):... Використовуйте для зберігання змінної у файл.

  • def get_stored_commands(self):... Отримати змінну, збережену у файлі.

  • def check_if_file_empty():... Перевір розмір файлу.

  • def run_commands(self, command):...Цей метод виступає компілятором python, який приймає команди, виконує обробку та виводить вихід для даної команди. Для запуску команд я рекомендував би використовувати, subprocess-moduleоскільки він забезпечує більш потужні засоби для нерестування нових процесів та отримання їх результатів; Для запуску віконних команд за допомогою python включає різні вбудовані бібліотеки, такі як;

    1. os ( докладно ), 2. підпроцес ( докладно ) тощо.

    Щоб перевірити, що краще використовувати, відвідайте посилання: підпроцес-модуль кращий, ніж os-модуль .

  • def start_terminal(self):...Цей метод просто включає функціонал для ініціювання tkinterвікна сеансу та показу базового макета для вікна введення та виводу.

    Ви можете додатково змінювати та оптимізувати цей код відповідно до вашої вимоги.


Режим роботи:

Цей простий tkinter GUI based python shellвиконати простий функціонал як Windows-командний рядок. Для запуску команд python directlyв командному рядку без переміщення в термінал python, ми робимо прості як;

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

Його результат був би простим як;

Hey Eleeza!!!

Аналогічно, для запуску декількох рядків безпосередньо за вказаний час;

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

Його вихід був би таким;

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

Тож використовувати це tkinter python shell;

  • Або ви можете розмістити команду як;

    import platform
    value=platform.uname()
    print('Value:', value)
  • або подібним чином;

    import platform;value=platform.uname();
    print('Value:', value)
  • або просто вбудована команда як

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

Ви отримаєте такий же результат.


1
Цей вхід / вихід ідеальний, дуже дякую! Хоча лише одне, якщо я призначу змінну, виконайте команду, а потім очистіть і спробуйте надрукувати змінну, це не зробить, то як би я отримав команду для запуску в реальному пітоні (якщо це не робити це)?
Елеза Чарівник персонажів

2
@Eleeza, насамперед ваше запитання не стосується такого типу вимог. Як це дуже просто input, output оболонка. Він діє як базовий python kernel. Він діє лише на те, що розміщено в текстовому тілі . Я не встановив жодного буфера чи кешу пам'яті для зберігання змінної історії. Дозвольте спочатку перевірити цю вимогу в цьому IDLE!
Мухаммед Усман

1
Що я збирався зробити, це гра, де весь світ запрограмований на основний код, а термінал використовується для його вивчення та взаємодії з ним. Я подумав, що це просто випадок виконання коду, перш ніж показати вихід, але мені доведеться це перевірити.
Елеза Чарівник персонажів

2
Докладно пояснено: я створюю своєрідний «світ» в основній ігровій програмі, де люди, місця тощо, всі вони об’єкти в Python. Гравець повинен орієнтуватися по світу через термінал GUI, використовуючи команди python, це гра про вивчення Python шляхом вивчення. Код насправді модифікується в реальному житті (але він після цього скидається).
Елеза Чарівник персонажів

8
@Eleeza, тут найкраще сховище github, яке може дати вам повний трек, на якому ви перебуваєте; Відвідайте посилання: github.com/codecombat/codecombat . Ось кілька інших посилань, які ви повинні мати на них і; github.com/replit/play , github.com/jatinmandav/Gaming-in-Python , github.com/PacktPublishing/-Learn-Python-Programming-with-Games , github.com/… , github.com/pyland/pyland , Ось ще одна довідка, а також github.com/CharlesPikachu/Games
Мухаммед Усман

4

Це проста оболонка, в основному використовується exec()для виконання операторів python та subprocess.Popen()для виконання зовнішньої команди:

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

Просто введіть звичайний оператор python:

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

Або введіть команду оболонки:

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

Ви також можете використовувати Upклавішу для виклику останньої команди.

Зверніть увагу, що якщо ви виконаєте системну команду, яка вимагає введення користувачем, оболонка буде заморожена протягом 5 секунд (використаний період часу communicate()).

Ви можете змінити on_key()функцію відповідно до ваших потреб.

Зауважте також, що використання exec()не є хорошою практикою.


Чи можете ви пояснити код, будь ласка? Я трохи початківець у всьому цьому ... і не знаю, чи правильно я розумію
Eleeza the Wizard Wizard

1
Я додав коментарі до свого коду, сподіваюся, що це допоможе.
acw1668

3

Я реалізував оболонку python, використовуючи code.InteractiveConsoleдля виконання команд для проекту. Нижче - спрощена версія, хоча все ще досить довга, тому що я писав прив'язки для спеціальних клавіш (наприклад, Return, Tab ...), щоб вони поводилися як на консолі пітона. Можна додати більше функцій, таких як автодоповнення jedi та підкреслення синтаксису пігментами.

Основна ідея полягає в тому, що я використовую push()метод the code.InteractiveConsoleдля виконання команд. Цей метод повертається, Trueякщо це часткова команда, наприклад def test(x):, і я використовую цей зворотний зв'язок, щоб вставити ...підказку, інакше відображається висновок і відображається новий >>>запит. Я фіксую вихід, використовуючи contextlib.redirect_stdout.

Також є багато коду, що включає позначки та порівняння індексів, тому що я заважаю користувачеві вставляти текст у раніше виконані команди. Ідея полягає в тому, що я створив позначку "введення", яка підказує мені, де починається активний запит, і self.compare('insert', '<', 'input')я можу знати, коли користувач намагається вставити текст над активним запитом.

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()
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.