Создание в Python приложения с графическим интерфейсом
Python это один из самых популярных языков программирования, в основном на нем пишут серверную часть сайтов и приложений. Но создать на нем программу с графическим интерфейсом не сложно. Для этих целей нужно воспользоваться специальной библиотекой. Их несколько, но выделить можно только Tkinter, wxPython, PySimpleGUI и PyQT. А из этих трех я советовал бы выбрать Tkinter так как она уже встроенная в Python и её не нужно устанавливать. Сегодня в качестве примера, сделаем сделаем приложение с графическим интерфейсом на Python.
Если только начинаете изучать Python следующие статьи будут вам очень полезны.
Программа на Python с графическим интерфейсом
Запускаем Python и создадим новый файл, для этого заходим в «File» и выбираем «New File».
В открывшемся окне уже можно писать код программы, для примера, сделаем форму разместим на ней кнопку, после нажатия на которую на форме будет что-то печататься.
#Вызываем графическую библиотеку
import tkinter as tk
#Создаем функцию для вывода текста
def add_lable():
lable = tk.Label(win, text = ‘Не так сильно ))) ‘)
lable.pack()#Создаем окно приложения
win = tk.Tk()
win.title(‘Приложение’)
win.geometry(«500×600+100+200»)
win.minsize(300,400)
win.maxsize(300,400)
win.resizable(True,True)#Добавляем кнопку
btn = tk.Button(win, text=»Нажми на МЕНЯ «,
command=add_lable
)
btn.pack()
win.mainloop()

Остается только запустить написанный код нажав F5 либо через меню «Run», предварительно нужно только сохранить данный файл.

Вот таким образом можно написать приложение на Python с графическим интерфейсом, это всего лишь один из вариантов.
programming is an art
В этом руководстве по программированию с графическим интерфейсом Python вы узнаете, как создавать программы с графическим интерфейсом с использованием инструментария Python Tkinter.
Графический интерфейс пользователя, также известный как GUI, является одной из лучших функций в программировании, которая делает программу визуально более привлекательной, чем обычные текстовые программы, покрытые черным или любым другим статическим цветом. GUI делает взаимодействие с пользователем намного проще и последовательнее.
Python GUI Программирование
Чтобы создать программу с графическим интерфейсом на Python, вам нужно использовать инструментарий GUI. Одним из широко используемых инструментов, доступных в Python, является Tkinter. Это один из самых стабильных и популярных инструментов GUI, который используют разработчики Python.
Установить модуль Tkinter
Модуль Tkinter по умолчанию доступен в операционной системе Windows. Однако, если вы используете любую другую операционную систему, вам нужно будет загрузить модуль отдельно.
Для ОС Windows
https://www.python.org/downloads/
Для ОС Linux
sudo apt-get установить python-tk (для Python 2.x)
sudo apt-get установить python3-tk (для Python 3.x)
Вы можете создавать элементы GUI, создавая экземпляры объектов из классов, предварительно определенных в модуле Tkinter, включенном в Tkinter Toolkit.
Некоторые элементы GUI в модуле Tkinter перечислены ниже.
Frame: он содержит другие элементы GUI, такие как Label, Button, TextBox и т. Д.
Метка: отображает не редактируемый текст или значки на макете экрана.
Кнопка: выполняет действие, когда пользователь активирует его или нажимает его с помощью мыши.
Ввод текста: он принимает одну строку текста и отображает ее.
Текстовое поле: этот элемент графического интерфейса принимает несколько строк текста и отображает его.
Кнопка проверки: позволяет пользователю выбрать или отменить выбор параметра.
Переключатель: эта кнопка позволяет пользователю выбрать один из нескольких вариантов.
Программы с графическим интерфейсом традиционно ориентированы на события. Управляемый событиями означает, что кнопки, значки или любой другой графический объект на экране реагируют на действия независимо от порядка их выполнения. Они реагируют на действия, выполняемые пользователем, а не на логический поток, как мы видели в текстовом программировании ранее.
Пример программирования на Python GUI
from Tkinter import *
root . title ( «My First GUI» )
root . geometry ( «500×500» )
root . mainloop ( )
Выход

Первая строка кода программы импортирует модуль Tkinter из библиотеки Python в namespce текущей программы.
Второй оператор используется для создания экземпляра объекта класса Tkinter Tk и назначается пользовательской переменной root.
Третий оператор использует метод title (), который описывает заголовок, отображаемый в строке заголовка окна. Он принимает строковый аргумент.
Третье утверждение использует метод geometry (), который используется для установки размеров окна. Он принимает строку (а не целые числа) в качестве аргументов,
представляющих ширину и высоту окна, разделенных символом «x».
Следующий оператор, наконец, запускает приложение окна GUI и ждет, пока пользователь введет команду.
Примечание . В программе Tkinter может быть только одно корневое окно. Если вы попытаетесь создать несколько окон в одной программе, ваша программа обречена на сбой.
Python Tkinter Frame
Фрейм — это виджет или базовый виджет, который используется для размещения в других виджетах, таких как метки, текстовые поля и другие. Это важный первый шаг, так как ничего не может быть сделано, если не разработаны рамки. Он в основном содержит другие виджеты.
Приложение = Рама (корень)
Здесь мы передали root в конструктор Frame. В результате новый фрейм помещается внутри корневого окна.
app.grid ()
Метод grid () — это тот, который есть у всех виджетов. Он связан с менеджером макета, который позволяет вам размещать виджеты в рамке.
Python Tkinter Label
Элементы графического интерфейса называются виджетами. Метка является одним из самых простых виджетов. Он состоит из не редактируемого текста или значков (или обоих). Он часто используется для обозначения других виджетов. Ярлыки не являются интерактивными. Вы не будете генерировать какую-либо команду, нажав на ярлык. Но метки важны для именования других виджетов, чтобы не было путаницы для конечного пользователя.
Как сделать программу на python с интерфейсом

Что мы изучим
- Как создавать базы данных, выполнять CRUD операции и запросы на языке SQL.
- Как обрабатывать события в элементах Listbox.
- Как назначить действия основным кнопкам программы и кнопке закрытия окна.
Скрипт для создания словаря
Словарь представляет собой базу данных SQLite, которая поставляется с Python по умолчанию. Для создания новой базы не придется устанавливать никакие дополнительные модули. Однако при желании можно установить набор дополнительных инструментов для работы с SQLite и один из визуальных браузеров-редакторов:
Структура таблицы словаря dictionary задается в sql_create_dictionary_table скрипта create_new_db.py:
- id – порядковый номер записи (целое число);
- word – иностранное слово (текстовое поле);
- meaning – значение слова на русском языке (текстовое поле).
При желании в этом описании можно задать поле для фонетической транскрипции. Для создания базы в определенной директории надо сначала убедиться, что необходимый путь к папке уже имеется на диске. Если нужную директорию не создать заранее, но указать путь к ней в коде, то выполнение скрипта завершится так:

SQL-запросы и команды
Первый запрос, который нам потребуется для извлечения из базы всех слов, выглядит так:

Для удаления слова необходимо выделить соответствующую строку. Слово извлекается из выделенной строки:

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

Функция callback_left отслеживает клики по английским словам в левом элементе Listbox. Когда пользователь кликает по слову, функция посылает запрос в базу:

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

Для этого нужно задать новый протокол:
self.wind.protocol("WM_DELETE_WINDOW", self.on_exit)
И добавить функцию:
def on_exit(self): if messagebox.askyesno("Выйти", "Закрыть программу?"): self.wind.destroy()
Это полный код модуля word_match.py для запоминания и проверки значений слов:
from tkinter import ttk from tkinter import * import random, os import sqlite3 class Match: db_name = 'dictionary.db' def __init__(self, window): self.wind = window self.wind.title('Учим слова') self.eng, self.trans = str(), str() self.message = Label(text = '', fg = 'red') self.message.grid(row = 1, column = 0, columnspan = 2, sticky = W + E) # правая и левая колонки self.left = Listbox(height = 12, exportselection=False, activestyle='none') self.left.grid(row = 2, column = 0) self.right = Listbox(height = 12, activestyle='none') self.right.grid(row = 2, column = 1) self.right.bind(">", self.callback_right) self.left.bind(">", self.callback_left) # назначение команд кнопкам программы и х-кнопке окна ttk.Button(text="Начать сначала", command=self.restart_program).grid(row = 4, column = 1, sticky = W + E) ttk.Button(text="Редактировать", command=self.run_edit).grid(row = 4, column = 0, sticky = W + E) self.wind.protocol("WM_DELETE_WINDOW", self.on_exit) # заполняем колонки словами self.get_words() # закрытие программы по клику на кнопке х def on_exit(self): if messagebox.askyesno("Выйти", "Закрыть программу?"): self.wind.destroy() # подключение к базе и передача запроса def run_query(self, query, parameters = ()): with sqlite3.connect(self.db_name) as conn: cursor = conn.cursor() result = cursor.execute(query, parameters) conn.commit() return result # запрос на извлечение всех существующих записей из базы в алфавитном порядке def get_words(self): query = 'SELECT * FROM dictionary ORDER BY word DESC' db_rows = self.run_query(query) # формирование словаря из перемешанных в случайном порядке слов и их значений lst_left, lst_right = [], [] for row in db_rows: lst_left.append(row[1]) lst_right.append(row[2]) random.shuffle(lst_left) random.shuffle(lst_right) dic = dict(zip(lst_left, lst_right)) # заполнение правой и левой колонок for k, v in dic.items(): self.left.insert(END, k) self.right.insert(END, v) # обработка клика по словам в левой колонке def callback_left(self, event): self.message['text'] = '' if not event.widget.curselection(): return # извлечение из базы значения выделенного слова w = event.widget idx = int(w.curselection()[0]) self.eng = w.get(idx) with sqlite3.connect(self.db_name) as conn: cursor = conn.cursor() sqlite_select_query = 'SELECT * from dictionary WHERE word = ?' cursor.execute(sqlite_select_query, (self.eng,)) record = cursor.fetchone() self.trans = record[2] # обработка клика в правой колонке def callback_right(self, event1): self.message['text'] = '' if not event1.widget.curselection(): return w = event1.widget idx = int(w.curselection()[0]) click = w.get(idx) # если выбранное слово является правильным переводом, удаляем и оригинал, и значение if click == self.trans: self.right.delete(ANCHOR) self.left.delete(ANCHOR) # сообщаем о неверном значении else: self.message['text'] = 'Неправильно' self.right.selection_clear(0, END) # загружаем окно и скрипт редактирования словаря def run_edit(self): os.system('edit_dictionary.py') # перезапуск программы def restart_program(self): self.message['text'] = '' self.left.delete(0, END) self.right.delete(0, END) self.get_words() if __name__ == '__main__': window = Tk() window.geometry('250x245+350+200') application = Match(window) window.mainloop()
Готовый проект доступен в репозитории .
Материалы по теме
- Как подружить Python и базы данных SQL. Подробное руководство
- SQL за 20 минут
- Руководство по SQL для начинающих. Часть 1: создание базы данных, таблиц и установка связей между таблицами
Использование интерфейсов и протоколов
В теории объектно-ориентированного программирования есть понятия интерфейсов и абстрактных классов. Эти классы созданы для того, чтобы быть отнаследованными в других классах. Интерфейс и абстрактный класс созданы для того, чтобы показать, какими свойствами и методами должны обладать все их дочерние классы. Разница интерфейса и абстрактного класса в том, что интерфейс не содержит реализации, а абстрактный класс может помимо абстрактных методов содержать и часть реализованных методов.
Использование интерфейсов и абстрактных классов — хорошая затея, если мы хотим заложить на будущее возможность замены компонентов системы на другие. Расширяемость системы это хорошо.
Например, допустим, мы хотим реализовать сохранение истории всех запросов погоды. Чтобы при каждом запуске нашей программы куда-то сохранялись её результаты, и в будущем можно было проанализировать эту информацию.
Куда мы можем сохранить эту информацию? В плоский txt-файл. В файл JSON. В базу данных SQL. В NoSQL базу данных. Отправить куда-то по сети в какой-то веб-сервис. Вариантов много и потенциально в будущем возможно нам захочется заменить текущий выбранный вариант на какой-то другой. Давайте реализуем модуль history.py , который будет отвечать за сохранение истории:
from weather_api_service import Weather class WeatherStorage: """Interface for any storage saving weather""" def save(self, weather: Weather) -> None: raise NotImplementedError def save_weather(weather: Weather, storage: WeatherStorage) -> None: """Saves weather in the storage""" storage.save(weather)
Здесь WeatherStorage — это интерфейс в терминах объектно-ориентированного программирования. Этот интерфейс описывает те методы, которые обязательно должны присутствовать у любого хранилища погоды. Собственно говоря, у любого хранилища погоды должен быть как минимум метод save , который принимает на вход погоду, которую он должен сохранить.
В интерфейсе WeatherStorage нет реализации (на то он и интерфейс), он только объявляет метод save , который должен быть определён в любом классе, реализующем этот интерфейс.
Функция save_weather будет вызываться более высокоуровневым управляющим кодом для сохранения погоды в хранилище. Эта функция принимает на вход погоду weather , которую надо сохранить, и реальный экземпляр хранилища storage , которое реализует интерфейс WeatherStorage .
Чтобы показать, что метод save интерфейса не реализован, мы возбуждаем в нём исключение NotImplementedError , эта ошибка говорит о том, что вызываемый метод не реализован. Таким образом, если мы создадим хранилище, отнаследованное от этого интерфейса, не реализуем в нём метод save и вызовем его, то у нас упадёт в рантайме исключение NotImplementedError :
class PlainFileWeatherStorage(WeatherStorage): pass storage = PlainFileWeatherStorage() storage.save() # Тут в runtime упадёт ошибка NotImplementedError
Проблема такого подхода в том, что ошибка, относящаяся к проверке типов (все ли методы интерфейса реализованы в наследующем его классе) падает только в рантайме. Хотелось бы, чтобы такая проверка выполнялась в IDE и статическим анализатором кода, а не падала в рантайме. Наша задача, напомню, сделать так, чтобы до рантайма ошибки не доходили.
Какой есть ещё вариант определения интерфейсов в Python? Есть вариант с использованием встроенного модуля ABC (документация), созданного как раз для работы с такими абстрактными классами и интерфейсами:
from abc import ABC, abstractmethod class WeatherStorage(ABC): """Interface for any storage saving weather""" @abstractmethod def save(self, weather: Weather) -> None: pass
Экземпляр класса, наследующего таким образом объявленный интерфейс, не получится создать без явной реализации всех методов, объявленных с декоратором @abstractmethod . То есть вот такой код в runtime упадёт сразу в момент создания экземпляра такого класса:
class PlainFileWeatherStorage(WeatherStorage): pass # Тут упадет ошибка в рантайме, так как в PlainFileWeatherStorage # не определен метод save storage = PlainFileWeatherStorage()
Опять же — код падает в runtime, пользователи видят ошибку, плохо. Как перенести проверку на корректность использования интерфейсов и абстрактных классов на IDE и статический анализатор кода?
Способ появился в Python 3.8 благодаря PEP 544, и он называется протоколами, Protocol :
from typing import protocol class WeatherStorage(Protocol): """Interface for any storage saving weather""" def save(self, weather: Weather) -> None: pass class PlainFileWeatherStorage: def save(self, weather: Weather) -> None: print("реализация сохранения погоды. ") def save_weather(weather: Weather, storage: WeatherStorage) -> None: """Saves weather in the storage""" storage.save(weather)
Воу! Класс PlainFileWeatherStorage никак не связан с WeatherStorage , не отнаследован от него, хотя и реализует его интерфейс в неявном виде, то есть просто определяет все функции, которые должны быть реализованы в этом интерфейсе. Сам интерфейс WeatherStorage отнаследован от класса typing.Protocol , что делает его так называемым протоколом. В функции save_weather тип аргумента storage по-прежнему установлен в этот интерфейс WeatherStorage .
Получается, что класс PlainFileWeatherStorage неявно реализует протокол/интерфейс WeatherStorage . Если вы работали с языком программирования Go — в нём интерфейсы реализованы схожим образом, это так называемая структурная типизация.
Почему использование такого подхода в приоритете? Потому что проверкой корректности использования интерфейсов занимается IDE и статический анализатор кода вроде mypy . Речь идёт уже не о проверке в runtime, речь идет о проверке корректности реализации до этапа, в котором участвуют пользователи программы. Это то, что нам нужно!
Таким образом, наш модуль history.py принимает следующий вид:
from datetime import datetime from pathlib import Path from typine import Protocol from weather_api_service import Weather from weather_formatter import format_weather class WeatherStorage(Protocol): """Interface for any storage saving weather""" def save(self, weather: Weather) -> None: raise NotImplementedError class PlainFileWeatherStorage: """Store weather in plain text file""" def __init__(self, file: Path): self._file = file def save(self, weather: Weather) -> None: now = datetime.now() formatted_weather = format_weather(weather) with open(self._file, "a") as f: f.write(f"\n\n") def save_weather(weather: Weather, storage: WeatherStorage) -> None: """Saves weather in the storage""" storage.save(weather)
PlainFileWeatherStorage это реализованное хранилище, отнаследованное от нашего интерфейса, то есть реализующее его методы. Помимо метода save этот класс реализует ещё конструктор, который сохраняет в поле self._file путь до файла, в который будет записываться информация о погоде.
Для перевода объекта погоды типа Weather в строку используется функция format_weather , которую мы реализовали ранее в модуле weather_formatter .
Этот код — абсолютно валиден с точки зрения проверки системы типов.
Вызовем теперь логику сохранения погоды в главном файле weather :
#!/usr/bin/env python3.10 from pathlib import Path from exceptions import ApiServiceError, CantGetCoordinates from coordinates import get_gps_coordinates from history import PlainFileWeatherStorage, save_weather from weather_api_service import get_weather from weather_formatter import format_weather def main(): try: coordinates = get_gps_coordinates() except CantGetCoordinates: print("Не смог получить GPS-координаты") exit(1) try: weather = get_weather(coordinates) except ApiServiceError: print("Не смог получить погоду в API-сервиса погоды") exit(1) save_weather( weather, PlainFileWeatherStorage(Path.cwd() / "history.txt") ) print(format_weather(weather)) if __name__ == "__main__": main()
Здесь мы создаём экземпляр объекта PlainFileWeatherStorage и передаём его на вход функции save_weather . Всё работает!

Для вывода содержимого текстового файла на скриншоте вместо cat использовался bat — продвинутый вариант:)
Теперь, если мы захотим изменить хранилище, мы можем создать новое хранилище, например, JSON-хранилище, реализовав в нём все методы интерфейса WeatherStorage , и передать это новое хранилище в save_weather . Всё продолжит работать и будет корректно с точки зрения типов. Причём нам не придётся ничего менять в функции save_weather , так как она опирается только на интерфейс, определённый в классе WeatherStorage .
history.py , добавленный код:
import json from typing import Protocol, TypedDict class HistoryRecord(TypedDict): date: str weather: str class JSONFileWeatherStorage: """Store weather in JSON file""" def __init__(self, jsonfile: Path): self._jsonfile = jsonfile self._init_storage() def save(self, weather: Weather) -> None: history = self._read_history() history.append(< "date": str(datetime.now()), "weather": format_weather(weather) >) self._write(history) def _init_storage(self) -> None: if not self._jsonfile.exists(): self._jsonfile.write_text("[]") def _read_history(self) -> list[HistoryRecord]: with open(self._jsonfile, "r") as f: return json.load(f) def _write(self, history: list[HistoryRecord]) -> None: with open(self._jsonfile, "w") as f: json.dump(history, f, ensure_ascii=False, indent=4)
Здесь мы воспользовались структурой TypedDict , типизированным словарём. Это удобно для нашего сценария, так как каждая запись погоды в JSON-файл будет представлять собой как раз структуру словаря, состоящую из двух полей — date для даты и времени получения погоды и weather для описания погоды. Метод _read_history предназначен для чтения данных погоды из JSON-файла и он возвращает не list[dict] , а list[HistoryRecord] , максимально конкретный тип данных. Аналогично метод _write принимает в качестве аргумента не list[dict] , а тоже list[HistoryRecord] . Везде используем максимально точную конкретную структуру данных.
weather , изменённый код:
from history import JSONFileWeatherStorage, save_weather def main(): # пропущено. save_weather( weather, JSONFileWeatherStorage(Path.cwd() / "history.json") ) print(format_weather(weather)) if __name__ == "__main__": main()

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