Как стать автором
Обновить

Дополняемый калькултор

Время на прочтение7 мин
Количество просмотров4.6K

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

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

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

Попробуйте поиграть с написанием функций и значениями переменных.

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

Теги:
Хабы:
+2
Комментарии14

Публикации

Изменить настройки темы

Истории

Работа

Python разработчик
132 вакансии
Data Scientist
60 вакансий

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн