Всем доброго времени суток! Когда я начинал учиться программированию, мой мечтой и главной целью было создание игры на 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. В нём написал вот такую функцию:

Запускаем калькулятор и делаем запрос:

Получаем резултат:

Попробуйте поиграть с написанием функций и значениями переменных.
Спасибо, что прочли эту статью! Надеюсь она была хоть немного полезной и интересной. Если остались вопросы, задавайте в комментарии, постараюсь ответить)
