Всем доброго времени суток! Когда я начинал учиться программированию, мой мечтой и главной целью было создание игры на Unity. Выполнив и перевыполнив этот план, создав две игры плюс прототип, я понял, что мой разум жаждет новых испытаний. Потакая этому желанию, я начал учить Python - язык, являющимся простым и мощным инструментом, черпающим силу из огромного множества библиотек. Достигнув в освоении анаконды питона определённого уровня, я решил попробовать написать на нём что-нибудь, что можно использовать на практике. Не став мудрить, я выбрал в качестве цели калькулятор, однако в необычной его ипостаси. Изюминкой моего творения должна стать возможность дополнять приложение функциями, описываемыми пользователем в формате текстового файла. ну и чтобы всё это выглядело по-божески, калькулятор будет иметь до безобразия простой графический интерфейс.
В этой статье я расскажу и покажу как повторить проделанную мной работу и создать дополняемый калькулятор. Буду крайне признателен критике и советам более опытных коллег.
Начнём с самого простого - интерфейса. Для его создания нам понадобится библиотека PyQt5 и приложение Qt Designer.
Библиотека устанавливается всем известным способом - через pip в командной строке.
pip install PyQt5
Приложение можно скачать с сайта build-system.fman.io, тыкнув на кнопку "СКАЧАТЬ".
Пройдя простой процесс установки, открываем приложение и видим рабочую область.
Чтобы начать создавать облик нашей программы, нажимаем в левом верхнем углу экрана File -> New.
После этого мы увидим диалоговое окно, предлагающее выбрать шаблон для создания формы. Выбираем Main Window.
Появится пустая форма, готовая к редактированию.
Наконец-то приступаем непосредственно к созданию интерфейса. Элементы добавляются в форму перетаскиванием из правой колонки, а параметры редактируются в левой.
Перетащим на форму три элемента: Plain Text Edit, Label и Push Button. После растянем до нужных размеров, уменьшим саму форму(это можно сделать потянув за её края). Выбрать элемент на форме можно тыкнув на него, отменить выделение - тыкнув по форме.
Теперь поколдуем с параметрами элементов. Выберем элемент Label. Изменим текст в строке "text", шрифт в строке "font" и добавим рамку в строке "frameShape"
Тем же способом изменим текст кнопки. Готовый интерфейс будет выглядеть так:
Сохраняем, получаем файл с расширением .ui. Чтобы с этим интерфейсом работать, его надо преобразовать в код на Python с помощью специальной утилиты. Для этого вводим следующую команду в консоль(в одну строку):
python -m PyQt5.uic.pyuic -x <полный путь к файлу БЕЗ расширения>.ui -o
<полный путь к файлу БЕЗ расширения>.py
После этой операции мы получим .py файл, в котором описан всё тот же интерфейс на языке программирования. В моём случае получился такой код:
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(431, 344)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setGeometry(QtCore.QRect(20, 190, 391, 91))
font = QtGui.QFont()
font.setPointSize(24)
self.pushButton.setFont(font)
self.pushButton.setObjectName("pushButton")
self.plainTextEdit = QtWidgets.QPlainTextEdit(self.centralwidget)
self.plainTextEdit.setGeometry(QtCore.QRect(20, 10, 391, 87))
self.plainTextEdit.setObjectName("plainTextEdit")
self.label = QtWidgets.QLabel(self.centralwidget)
self.label.setGeometry(QtCore.QRect(20, 110, 391, 61))
font = QtGui.QFont()
font.setPointSize(10)
self.label.setFont(font)
self.label.setFrameShape(QtWidgets.QFrame.Box)
self.label.setObjectName("label")
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 431, 26))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.pushButton.setText(_translate("MainWindow", "ПОСЧИТАТЬ"))
self.label.setText(_translate("MainWindow", "null"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
Внутри класса описываются все элементы интерфейса, а внутри условного оператора происходит запуск приложения при условии, если вы запустили именно этот файл. Можете попробовать запустить это файл. Результатом будет запуск оконного приложения с созданным нами интерфейсом, которое пока что ни на что не способно.
Всё, с интерфеёсом закончили, переходим к логике.
Полный код основного файла выглядит так:
#импорт библиотек и модулей
from calc_interface import *
from open_function import Function
import sys
#функция, срабатываемая при нажатии кнопки
def click():
string = ""
if ui.plainTextEdit.toPlainText() != "":
if ui.plainTextEdit.toPlainText()[0] != "$":
string = eval(ui.plainTextEdit.toPlainText())
else: string = Function(ui.plainTextEdit.toPlainText())
ui.label.setText(string)
ui.plainTextEdit.setPlainText("")
#запуск приложения
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
ui.pushButton.clicked.connect(click)
sys.exit(app.exec_())
Теперь более детально. Вначале импортируем библиотеку sys, файл с нашим интерфейсом, и файл с открытием кода функции, который мы рассмотрим чуть ниже.
from calc_interface import *
from open_function import Function
import sys
Далее пишем функцию, которую активирует кнопка.
def click():
string = ""
if ui.plainTextEdit.toPlainText() != "":
if ui.plainTextEdit.toPlainText()[0] != "$":
string = eval(ui.plainTextEdit.toPlainText())
else: string = Function(ui.plainTextEdit.toPlainText())
ui.label.setText(string)
ui.plainTextEdit.setPlainText("")
Во второй строке объявляем переменную, в которую будем записывать результат вычислений. В строках 3-6 производим вычисление. В 7 строке выводим результат в модуль Label. В 8 строке очищаем поле ввода.
Далее идут строки, отвечающие непосредственно за запуск приложения.
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
ui.pushButton.clicked.connect(click)#Эту строку надо будет дописать
sys.exit(app.exec_())
Все строки кроме выделенной можно скопировать из файла с интерфейсом(они находятся внутри условного оператора в самом низу). Выделенную строку же придётся дописать. Она отвечает за привязку написанной нами функции к кнопке.
На данном этапе наше приложение уже может выполнять функции обычного калькулятора. Попробуем запустить файл с логикой. В появившемся окне в поле ввода введём математическое выражение и нажмём кнопку "ПОСЧИТАТЬ". Поле ввода очистится, а в элементе Label отобразится результат вычислений.
С вычислениями кстати у меня произошла довольно забавная история. Дело в том, что раньше я использовал более низкоуровневый язык - C#. В результате при переходе на Python я не знал о большей части его функций, которые в C# приходилось писать самому. Не знал я и о функции eval(), отвечающей в этой программе за все вычисления. Ну и пошёл я её писать, чего тянуть то? В итоге, спустя 150 строк кода, кучу времени и нервов я узнаю, что всё что я написал уже есть в питоне "из коробки"... Из плюсов, так как написанная функция счёта строки больше не нужна, код программы сократился раза в 2, плюс есть материал на ещё одну статью. А теперь вернёмся к коду.
Теперь, когда калькулятор умеет считать и "общаться" с пользователем посредством UI, мы можем перейти к самому интересному - записи функций и их чтению приложением.
Вкратце опишу как это будет работать. В определённой папке лежат текстовые файлы, внутри которых описаны функции для калькулятора. Имя файла является именем функции. Функцией же является строка, в правой части которой описаны переменные, а в левой находится уравнение. Вот пример функции:
a:b:c;2*(a+1)+(b+c+(b+c))
Чтобы использовать функцию в калькуляторе, мы должны её вызвать из поля набора текста. Запрос выглядит так: $<имя файла без расширения>:<переменные через запятую>. Программа считает из файла уравнение, подставит на место переменных введённые числа, решит как обычный пример и выдаст результат.
Код модуля, отвечающего за использование функций, выглядит так:
#подставление значений переменных
def construct(equ, variables):
count = 0
for i in equ:
if i in "qwertyuioasdfghjklzxcvbnm":
for j in variables:
if j[0] == i:
equ[count] = j[1]
break
count += 1
return equ
#основная функция
def Function(string):
string = string.split("$")[1]
path = "C:\\fc\\" + string.split(":")[0] + ".txt"
variables = string.split(":")[1].split(",")
file = open(path, "r")
equation = file.read().split(";")
e_vars = equation[0].split(":")
arr_vars = []
count = 0
for i in e_vars:
arr_vars.append([e_vars[count], variables[count]])
count += 1
equality = construct(list(equation[1]), arr_vars)
file.close()
return str(eval("".join(equality)))
Теперь подробно. Вначале рассмотрим функцию Function(лучшее название), которая вызывается из основного файла.
def Function(string):
string = string.split("$")[1]
path = "<путь к любой папке>" + string.split(":")[0] + ".txt"
variables = string.split(":")[1].split(",")
file = open(path, "r")
equation = file.read().split(";")
e_vars = equation[0].split(":")
arr_vars = []
count = 0
for i in e_vars:
arr_vars.append([e_vars[count], variables[count]])
count += 1
equality = construct(list(equation[1]), arr_vars)
file.close()
return str(eval("".join(equality)))
В строках 2-4 происходит считывание запроса, составление пути к файлу и запись значений переменных. В строках 5-7 уже из файла функции достаются названия переменных и уравнение. В строках 8-12 создаётся двумерный список, элементами которого являются списки, хранящие имя переменной и значение. Строка 13 превращает уравнение в математическое выражение, заменяя имена переменных на их значения через написанную выше функцию. 14 строка закрывает файл, 15 возвращает результат вычислений.
Примечание: из-за особенностей реализации имена переменных в файле должны содержать только одну букву.
Калькулятор полностью готов! Попробуем написать и вызвать функцию. В папке C:\fc\(путь к именно этой папке должен быть в вашем коде, у меня это 'C:\\fc\\') я создал файл test.txt. В нём написал вот такую функцию:
Запускаем калькулятор и делаем запрос:
Получаем резултат:
Попробуйте поиграть с написанием функций и значениями переменных.
Спасибо, что прочли эту статью! Надеюсь она была хоть немного полезной и интересной. Если остались вопросы, задавайте в комментарии, постараюсь ответить)
Комментарии (14)
LeshaRB
07.02.2022 21:40+1Статья чем-то напомнила книги 90-ых
Язык программирования ХХХ для чайников...
unsignedchar
07.02.2022 22:57Оно и есть. Только в 90-х визуальные конструкторы были интуитивно понятные, а этот qt creator как то не очень.
sshmakov
08.02.2022 00:13Чтобы с этим интерфейсом работать, его надо преобразовать в код на Python с помощью специальной утутилиты.
Не надо. Используйте uic.loadUi.
unsignedchar
08.02.2022 10:40Используйте uic.loadUi
И простыню кода для привязки сигналов - руками. Как то в старинных Delphi это автоматически делалось.
sshmakov
08.02.2022 13:08И простыню кода для привязки сигналов - руками.
Нет, для этого существуют автоконнект по имени и декораторы pyqtSlot.
@pyqtSlot(str, name="on_lineEdit_textChanged") def textChanged(self, text: str): do_it_what_you_want()
unsignedchar
08.02.2022 23:55Простыня с декораторами чуть меньше. Но всё равно её писать вручную.
sshmakov
09.02.2022 01:01Это вы сейчас всё ради сравнения "Python хуже Delphi"? Тогда это не ко мне, я в этой ветке говорю только про питон.
unsignedchar
09.02.2022 08:45Python (язык) местами лучше чем Delphi (язык). А то, что для конструирования GUI ничего лучше чем qt creator не придумали - как то странно это.
Matisumi
08.02.2022 08:53А кто-то еще в промышленной разработке использует Qt? На мой взгляд в 2022 году он выглядит как неких анахронизм. Плюс, как я понимаю, на нем не сделать нормальный масштабируемый интерфейс, как, например в WPF
Cfyz
09.02.2022 06:20А какие, желательно кроссплатформенные, альтернативы вы могли бы привести в пример?
Matisumi
09.02.2022 10:40Если мне нужно сделать что-то кроссплатформенное с десктопным интерфейсом, я выберу тот же WPF и C#. Или AvaloniaUI. Или Electron, если мне не подходит стек C#.
Просто тут как - на мой взгляд задачи надо решать исходя из требований задачи. Зачем забивать гвозди микроскопом (python + qt), если есть молоток (все, что я выше перечислил)
Jury_78
Увидев в названии "Дополняемый калькултор" - вот что то новое решил я, но нет - просто опечатка. :)