Лучший способ структурировать приложение tkinter?

136

Ниже приведена общая структура моей типичной программы Python tkinter.

def funA():
    def funA1():
        def funA12():
            # stuff

    def funA2():
        # stuff

def funB():
    def funB1():
        # stuff

    def funB2():
        # stuff

def funC():
    def funC1():
        # stuff

    def funC2():
        # stuff


root = tk.Tk()

button1 = tk.Button(root, command=funA)
button1.pack()
button2 = tk.Button(root, command=funB)
button2.pack()
button3 = tk.Button(root, command=funC)
button3.pack()

funA funBи funCвызовет другие Toplevelокна с виджетами, когда пользователь нажмет кнопку 1, 2, 3.

Мне интересно, если это правильный способ написания программы Python Tkinter? Конечно, это сработает, даже если я напишу так, но лучше ли? Звучит глупо, но когда я вижу коды, написанные другими людьми, их код не перепутается с кучей функций, и в основном у них есть классы.

Есть ли какая-то конкретная структура, которой мы должны следовать в качестве хорошей практики? Как я должен планировать, прежде чем начать писать программу на Python?

Я знаю, что нет лучшей вещи в программировании, и я не прошу об этом. Я просто хочу несколько советов и объяснений, чтобы держать меня в правильном направлении, поскольку я изучаю Python самостоятельно.

Крис Аунг
источник
2
Вот отличное руководство по дизайну графического интерфейса tkinter с парой примеров - python-textbok.readthedocs.org/en/latest/… Вот еще один пример с шаблоном проектирования MVC - sukhbinder.wordpress.com/2014/12/ 25 /…
Бондолин
12
Этот вопрос может быть широким, но он полезен и является относительно популярным ответом (относительно почти всех других ответов [tkinter]). Я назначаю, чтобы открыть снова, поскольку я вижу, что это открыто, более полезно чем закрытие этого.
Брайан Оукли

Ответы:

271

Я защищаю объектно-ориентированный подход. Это шаблон, с которого я начинаю:

# Use Tkinter for python 2, tkinter for python 3
import tkinter as tk

class MainApplication(tk.Frame):
    def __init__(self, parent, *args, **kwargs):
        tk.Frame.__init__(self, parent, *args, **kwargs)
        self.parent = parent

        <create the rest of your GUI here>

if __name__ == "__main__":
    root = tk.Tk()
    MainApplication(root).pack(side="top", fill="both", expand=True)
    root.mainloop()

Важные вещи, на которые стоит обратить внимание:

  • Я не использую импорт подстановочных знаков. Я импортирую пакет как «tk», который требует, чтобы я поставил перед всеми командами префикс tk.. Это предотвращает глобальное загрязнение пространства имен, а также делает код совершенно очевидным, когда вы используете классы Tkinter, классы ttk или некоторые другие.

  • Основное приложение - это класс . Это дает вам личное пространство имен для всех ваших обратных вызовов и частных функций, и, как правило, упрощает организацию вашего кода. В процедурном стиле вы должны кодировать сверху вниз, определять функции перед их использованием и т. Д. С помощью этого метода вы этого не делаете, поскольку вы фактически не создаете главное окно до самого последнего шага. Я предпочитаю наследовать tk.Frameтолько потому, что обычно начинаю с создания фрейма, но это ни в коем случае не является необходимым.

Если в вашем приложении есть дополнительные окна верхнего уровня, я рекомендую сделать каждый из них отдельным классом, унаследованным от tk.Toplevel. Это дает вам все те же преимущества, что и упомянутые выше: окна являются атомарными, у них есть собственное пространство имен, а код хорошо организован. Кроме того, он позволяет легко помещать каждый в отдельный модуль, когда код начинает увеличиваться.

Наконец, вы можете рассмотреть возможность использования классов для каждой основной части вашего интерфейса. Например, если вы создаете приложение с панелью инструментов, панелью навигации, строкой состояния и основной областью, вы можете создать каждый из этих классов. Это делает ваш основной код довольно маленьким и легким для понимания:

class Navbar(tk.Frame): ...
class Toolbar(tk.Frame): ...
class Statusbar(tk.Frame): ...
class Main(tk.Frame): ...

class MainApplication(tk.Frame):
    def __init__(self, parent, *args, **kwargs):
        tk.Frame.__init__(self, parent, *args, **kwargs)
        self.statusbar = Statusbar(self, ...)
        self.toolbar = Toolbar(self, ...)
        self.navbar = Navbar(self, ...)
        self.main = Main(self, ...)

        self.statusbar.pack(side="bottom", fill="x")
        self.toolbar.pack(side="top", fill="x")
        self.navbar.pack(side="left", fill="y")
        self.main.pack(side="right", fill="both", expand=True)

Поскольку все эти экземпляры имеют общего родителя, родительский элемент фактически становится частью «контроллера» в архитектуре модель-представление-контроллер. Так, например, главное окно может поместить что-то в строку состояния, вызвав вызов self.parent.statusbar.set("Hello, world"). Это позволяет вам определить простой интерфейс между компонентами, помогая поддерживать связь с минимальным.

Брайан Оукли
источник
22
@ Брайан Оукли, ты знаешь какие-нибудь хорошие примеры кодов в интернете, чтобы я мог изучить их структуру?
Крис Аунг
2
Я второй объектно-ориентированный подход. Однако, по моему опыту, воздержание от использования наследования в вашем классе, который вызывает GUI, является хорошей идеей. Он предлагает вам больше гибкости, если оба объекта Tk и Frame являются атрибутами класса, который не наследует ни от чего. Таким образом, вы можете получить доступ к объектам Tk и Frame более легко (и менее двусмысленно), и уничтожение одного не разрушит все в вашем классе, если вы этого не хотите. Я забыл точную причину, почему это важно в некоторых программах, но это позволяет вам делать больше вещей.
Brōtsyorfuzthrāx
1
не будет ли просто наличие класса дать вам личное пространство имен? почему подклассы Frame улучшают это?
ГКБ
3
@gcb: да, любой класс даст вам личное пространство имен. Почему подкласс Frame? Обычно я собираюсь создать фрейм в любом случае, так что управлять им будет на один класс меньше (подкласс фрейма против класса, наследуемого от объекта, с фреймом в качестве атрибута). Я слегка перефразировал ответ, чтобы прояснить это. Спасибо за ответ.
Брайан Оукли
2
@madtyn: нет необходимости сохранять ссылку parent, если вы не собираетесь использовать ее позже. Я не сохранил его, потому что ни один код в моем примере не требовал его сохранения.
Брайан Оукли
39

Помещение каждого из ваших окон верхнего уровня в отдельный класс обеспечивает повторное использование кода и лучшую организацию кода. Все кнопки и соответствующие методы, присутствующие в окне, должны быть определены внутри этого класса. Вот пример (взятый отсюда ):

import tkinter as tk

class Demo1:
    def __init__(self, master):
        self.master = master
        self.frame = tk.Frame(self.master)
        self.button1 = tk.Button(self.frame, text = 'New Window', width = 25, command = self.new_window)
        self.button1.pack()
        self.frame.pack()
    def new_window(self):
        self.newWindow = tk.Toplevel(self.master)
        self.app = Demo2(self.newWindow)

class Demo2:
    def __init__(self, master):
        self.master = master
        self.frame = tk.Frame(self.master)
        self.quitButton = tk.Button(self.frame, text = 'Quit', width = 25, command = self.close_windows)
        self.quitButton.pack()
        self.frame.pack()
    def close_windows(self):
        self.master.destroy()

def main(): 
    root = tk.Tk()
    app = Demo1(root)
    root.mainloop()

if __name__ == '__main__':
    main()

Также см:

Надеюсь, это поможет.

alecxe
источник
6

Это не плохая структура; это будет работать просто отлично. Тем не менее, вам нужно иметь функции в функции для выполнения команд, когда кто-то нажимает на кнопку или что-то

Так что вы могли бы написать классы для них, а затем иметь методы в классе, которые обрабатывают команды для нажатий кнопок и тому подобное.

Вот пример:

import tkinter as tk

class Window1:
    def __init__(self, master):
        pass
        # Create labels, entries,buttons
    def button_click(self):
        pass
        # If button is clicked, run this method and open window 2


class Window2:
    def __init__(self, master):
        #create buttons,entries,etc

    def button_method(self):
        #run this when button click to close window
        self.master.destroy()

def main(): #run mianloop 
    root = tk.Tk()
    app = Window1(root)
    root.mainloop()

if __name__ == '__main__':
    main()

Обычно программы с несколькими окнами представляют собой несколько больших классов, и во __init__всех записях создаются метки и т. Д., А затем каждый метод обрабатывает события нажатия кнопки.

На самом деле не существует правильного способа сделать это, что бы ни работало для вас и выполняло работу, пока она читаема, и вы можете легко объяснить это, потому что, если вы не можете легко объяснить свою программу, возможно, есть лучший способ сделать это. ,

Взгляните на мышление в Tkinter .

последовательный
источник
3
«Мышление в Tkinter» выступает за мировой импорт, что я считаю очень плохим советом.
Брайан Оукли
1
Это правда, я не предлагаю вам использовать глобальные переменные, только некоторые из основных методов структуры класса, вы правы :)
Serial
2

ООП должен быть подходом и frameдолжен быть переменной класса вместо переменной экземпляра .

from Tkinter import *
class App:
  def __init__(self, master):
    frame = Frame(master)
    frame.pack()
    self.button = Button(frame, 
                         text="QUIT", fg="red",
                         command=frame.quit)
    self.button.pack(side=LEFT)
    self.slogan = Button(frame,
                         text="Hello",
                         command=self.write_slogan)
    self.slogan.pack(side=LEFT)
  def write_slogan(self):
    print "Tkinter is easy to use!"

root = Tk()
app = App(root)
root.mainloop()

введите описание изображения здесь

Ссылка: http://www.python-course.eu/tkinter_buttons.php

Тревор
источник
2
Вы можете использовать только TKinterна Python 2. Я бы порекомендовал использовать tkinterдля Python 3. Я бы также поместил последние три строки кода в main()функцию и вызвал ее в конце программы. Я бы определенно избегал его использования, from module_name import *поскольку он загрязняет глобальное пространство имен и может снизить читабельность.
Зак
1
Как вы можете определить разницу между button1 = tk.Button(root, command=funA)и button1 = ttk.Button(root, command=funA)если tkinterмодуль расширения также импортируется? С *синтаксисом обе строки кода будут выглядеть так button1 = Button(root, command=funA). Я бы не рекомендовал использовать этот синтаксис.
Зак
0

Организация вашего приложения с использованием классов позволяет вам и другим, кто работает с вами, легко отлаживать проблемы и улучшать приложение.

Вы можете легко организовать свое приложение так:

class hello(Tk):
    def __init__(self):
        super(hello, self).__init__()
        self.btn = Button(text = "Click me", command=close)
        self.btn.pack()
    def close():
        self.destroy()

app = hello()
app.mainloop()
muyustan
источник
-2

Вероятно, лучший способ научиться структурировать вашу программу - это читать код других людей, особенно если это большая программа, в которую многие люди внесли свой вклад. Посмотрев код многих проектов, вы должны понять, каким должен быть консенсусный стиль.

Python, как язык, особенный в том, что есть несколько строгих рекомендаций относительно того, как вы должны форматировать свой код. Первый - это так называемый «Zen of Python»:

  • Красиво лучше, чем безобразно.
  • Явное лучше, чем неявное.
  • Простое лучше, чем сложное.
  • Сложный лучше, чем сложный.
  • Квартира лучше, чем вложенная.
  • Разреженный лучше, чем плотный.
  • Читаемость имеет значение.
  • Особые случаи не достаточно особенные, чтобы нарушать правила.
  • Хотя практичность превосходит чистоту.
  • Ошибки никогда не должны проходить бесшумно.
  • Если явно не молчать.
  • Перед лицом двусмысленности откажитесь от соблазна гадать.
  • Должен быть один - и желательно только один - очевидный способ сделать это.
  • Хотя этот путь поначалу может быть неочевидным, если вы не голландец.
  • Сейчас лучше, чем никогда.
  • Хотя никогда зачастую лучше , чем прямо в настоящее время.
  • Если реализацию сложно объяснить, это плохая идея.
  • Если реализацию легко объяснить, это может быть хорошей идеей.
  • Пространства имен - одна из отличных идей - давайте сделаем больше!

На более практическом уровне есть PEP8 , руководство по стилю для Python.

Имея это в виду, я бы сказал, что ваш стиль кода не очень подходит, особенно для вложенных функций. Найдите способ сгладить их, используя классы или перемещая их в отдельные модули. Это сделает структуру вашей программы намного проще для понимания.

Инбар Роуз
источник
12
-1 для использования Zen of Python. Хотя это все хороший совет, он не имеет прямого отношения к заданному вопросу. Исключите последний абзац, и этот ответ может относиться практически к каждому вопросу о питоне на этом сайте. Это хороший, позитивный совет, но он не отвечает на вопрос.
Брайан Оукли
1
@BryanOakley Я не согласен с вами по этому поводу. Да, Zen of Python является широким и может использоваться для решения многих вопросов. Он упомянул в последнем абзаце выбор классов или размещение функций в отдельных модулях. Он также упомянул PEP8, руководство по стилю для Python, со ссылками на него. Хотя это и не прямой ответ, я думаю, что этот ответ заслуживает доверия в связи с тем, что в нем упоминается множество различных путей, которые могут быть приняты. Это только мое мнение
Зак
1
Я пришел сюда в поисках ответов на этот конкретный вопрос. Даже для открытого вопроса, я ничего не могу сделать с этим ответом. -1 также от меня.
Джонатан
Ни в коем случае, вопрос не в том, чтобы структурировать приложение tkinter, а в том , что касается руководства по стилю, кодированию и дзен. Легко, как процитировать @Arbiter "Хотя это и не прямой ответ", так что это не ответ. Это как «возможно да, а может и нет», с добавлением дзен.
m3nda
-7

Я лично не использую возражающий ориентированный подход, главным образом потому что это a) только мешает; б) вы никогда не будете использовать это как модуль.

но то, что здесь не обсуждается, это то, что вы должны использовать многопоточность или многопоточность. Всегда. в противном случае ваше приложение будет ужасным.

просто сделайте простой тест: запустите окно, а затем загрузите какой-нибудь URL или что-нибудь еще. изменения в том, что ваш пользовательский интерфейс не будет обновляться во время выполнения сетевого запроса. Это означает, что окно вашего приложения будет разбито. зависит от операционной системы, в которой вы находитесь, но в большинстве случаев она не будет перерисовываться, все, что вы перетаскиваете через окно, будет намазано на нем, пока процесс не вернется к основной петле TK.

GCB
источник
4
То, что вы говорите, просто не соответствует действительности. Я написал множество приложений, основанных на tk, как личных, так и коммерческих, и почти никогда не использовал потоки. Потоки имеют свое место, но это просто неправда, что вы должны использовать их при написании программ tkinter. Если у вас есть длинные выполняемые функции, вам могут понадобиться потоки или многопроцессорная обработка, но вы можете написать множество программ, которые не нуждаются в потоках.
Брайан Оукли
Я думаю, что если бы вы перефразировали свой ответ, чтобы быть более ясным, это был бы лучший ответ. Также было бы очень полезно иметь канонический пример использования потоков с tkinter.
Брайан Оукли
не заботился о том, чтобы быть лучшим ответом здесь, потому что это своего рода не по теме. но имейте в виду, что начинать с многопоточности очень просто. если вы добавите позже, это проигранная битва. и в настоящее время нет абсолютно никакого приложения, которое никогда не будет общаться с сетью. и даже если вы игнорируете и думаете, что «у меня мало дискового ввода-вывода», завтра ваш клиент решит, что файл будет жить в NFS, а вы ожидаете сетевого ввода-вывода и ваше приложение кажется мертвым.
декабря
2
@ erm3nda: «каждое приложение, связанное с сетью или выполняющее запись ввода-вывода, будет намного быстрее, используя потоки или подпроцесс» - это просто неправда. Потоки не обязательно сделают вашу программу быстрее, а в некоторых случаях сделают ее медленнее. В программировании GUI основная причина использования потоков состоит в том, чтобы иметь возможность запускать некоторый код, который иначе блокировал бы GUI.
Брайан Оукли
2
@ erm3nda: нет, я не говорю потоки не нужны вообще . Они определенно необходимы (ну, потоки или многопроцессорность) для многих вещей. Просто есть очень большой класс приложений с графическим интерфейсом, где tkinter подходит, но где потоки просто не нужны. И да, «установщики, блокноты и другие простые инструменты» попадают в эту категорию. Мир состоит из большего количества этих «простых инструментов», чем слова, Excel, Photoshop и т. Д. Кроме того, помните, что контекст здесь tkinter . Tkinter обычно не используется для очень больших и сложных приложений.
Брайан Оукли