Всем привет! В данной статье вы узнаете как создать свой собственный калькулятор на python с использованием большой библиотеки pyqt5.
Для начала, если у вас нет python, переходим на сайт: https://www.python.org/ и скачиваем версию 3.9. На данный момент имеется версия 3.10, но на ней не работает множество библиотек. Далее устанавливаем интересующую вас среду разработки. Я использую pycharm. Как по мне, самый удобный инструмент, при том, что имеется бесплатная лицензия, а платную лицензию можно получить бесплатно, выполняя курсы на платформе stepik. Далее устанавливаем pyqt5:
- прописываем в консоли: pip install pyqt5;
- Если вам понадобится в будущем русская версия qt designer, то прописываем ещё в консоль: pip install pyqt5-tools, программа будет лежать в папке: Python39\Lib\site-packages\qt5_applications\Qt\bin\designer.exe.
В общем всё готово, можем приступать!
Весь калькулятор будет основан на работе функции eval. Данная функция принимает, в качестве аргумента, строку, и если математическое условие введено верно, то функция возвращает double число.
Создаём класс окна программы и в конструкторе (def __init__(self):) прописываем все необходимые переменные, а так же вызываем функцию self.initUI() в которой у нас прописаны все виджеты, и следом отображаем окно, с помощью self.show(). Так же при необходимости окно можно скрыть с помощь self.hide(), но не в этом уроке.
self._result - булевая переменная, которая показывает, был выведен результат или пользователь продолжает ввод.
self._symbol - так же булевая переменная, которая хранит в себе состояние ввода символов взаимодействия с числами (+, -, / и тд).
self._line = '' - собственно то str переменная, которая хранит в себе введённую строку, которую нужно посчитать.
Перейдём к функции initUI():
Так как у нас класс унаследован от QWidget, то мы работаем на прямую через .
self. self.setFixedSize(wight, height) - указывает неизменяемую ширину и высоту окна. Существует функция .resize(wight, height), которая позволяет менять размер окна мышкой.
self.setWindowFlags(Qt.Window | Qt.WindowCloseButtonHint) - Указывает флаги окна. Собственно первый аргумент является типом (их много, можно посмотреть в документации), а остальные аргументы, являются модулями окна. В данном случае мы создаём окно только с кнопкой "закрыть".
self.setWindowTitle('Калькулятор') - устанавливает имя окна.
self.gridLayout = QWidget(self) - создаёт ещё одну поверхность, только уже для кнопок.
self.gridLayout.setGeometry(QRect(10, 95, 260, 340)) - указывает размер новой поверхности, через QRect.
.setObjectName('gridLayout') - задаёт имя объекта.
self.grid = QGridLayout(self.gridLayout) - создаёт сетку на заранее созданной для неё поверхности. Поверхность указывается в скобках.
self.grid.setContentsMargins(0, 0, 0, 0) - устанавливает сетку состояний положений и видимостей объектов.
.setGeometry(10, 10, 260, 75) - задаёт положение и размер виджета.
Переходим к кнопкам:
Далее пойдём описание по строкам:
30 - используя генератор списков, мы создаём список, который хранит в себе ещё 5 списков, размер каждого из которых, равен 4. Обращаем внимание на (i, j, 1, 1) - первые 2 элемента кортежа которого, в себе хранят положение на сетке, а остальные 2 обозначают видимость.
31 - Надписи на все кнопки. Последний элемент пуст, так как там будет большая кнопка
36 - параллельно проходимся по всем элементам списка позиций и списка имён.
37 - если попадается пустая кнопка, то пропускаем эту итерацию (38).
39 - создаёт кнопку на выбранной поверхности.
40 - размер одной кнопки.
41, 42 - тут задаём фиксированное значение кнопки, так как окно не изменяет размеры.
43 - если мы итерируем кнопку подсчёта, то увеличиваем её размер до двух клеток (44).
45 - добавляем кнопку в сетку с заранее сгенерированной позицией.
46 - задаём кнопке имя.
47 - подключаем к кнопке связь на нажатие. В случая нажатия, будет вызываться функция, в которой у нас и будут происходить вычисления.
Как раз переходим к этой функции и разобьём её на 2 части:
50 - так как у нас, при объявлении, все кнопки имели одинаковые имена, то и получать информацию через обращение по имени, не получится. По этому мы используем self.sender() - которая и является кнопкой, которая вызвала эту функцию. Получаем текст с кнопки.
51 - получаем текст с дисплея ввода/вывода.
52 - проверяем, является ли переданное с кнопки значение, числом.
53 - обнуление всех переменных, если был получение результат и пользователь попробовал продолжить вводить цифры.
57 - если до этого вводился символ бинарного оператора, то меняем состояние, для возможного в дальнейшем ввода.
59 - если дисплей пустой, то просто присваиваем ему число.
60 - если значение на дисплее в int виде, то обрезаем .0, чтобы добавить ещё цифру к текущему значению, имена получится 6.08 вместо 68.
65 - добавляем значение к сроке примера.
66 - изменяем значение на дисплее.
67 - обработка бинарных операторов.
68 - если символ не был введён. Что избежать повторного ввода символов.
69 - если уже был получен результат, но вы хотите продолжить работу с полученным числом.
71 - если был получен символ возведения в степень, то 72 - добавляем **, так как именно так в питоне происходит возведение в нужную степень.
73 - добавляем символы не подошедшие под условия в конец строки.
75 - указываем, что символ уже был введён.
76 - очищаем дисплей, для ввода нового числа.
Вторая часть функции:
Так как eval подвержен частым ошибкам из за неправильного синтаксиса, то сразу помещаем весь код под блок try: except:. Блок except - выполнится в случае возникновения любой из ошибок в блоке try.
Так как никакое из этих действий не требует дополнительных аргументов для продолжения вычисления, то в 83 строке, мы сразу подсчитываем строку в число и возвращаем его обратно в str виде. Так же, если были обнаружены .0 в конце строки, то просто их обрезаем. Советую изучить срезы, они очень сильно уменьшают код и делают его более читаемым.
86 - действие на "корень". 87 - чтобы не подключать дополнительные библиотеки, мы можем просто представить корень, как возведение в степень 1/2. 88 - Считаем результат, и так же, в 89 строке, срезаем лишние .0.
91 - действие на "факториал". 92 - многим известно, что факториал 0 = 1, так что это учитываем тоже. Иначе 96 - делаем цикл до введённого числа, с каждой итерацией которого умножаем уже имеющееся значение, на значение итерации. 98 - сохраняем значение.
100 - если число слишком длинное, то мы укорачиваем его с вида: 10000000, до вида: 1 Е7. Следом, в 100 строке, мы выводим укороченное значение на экран.
103, 104 - отключаем состояние символов, позволяя ввести ещё символы, для работы с полученным числом, и запоминаем, что был получен результат.
_ - перед функцией или переменной, обозначает инкапсуляцию. Простым языком: нигде, кроме самой функции/класса, вы не сможете работать с функциями/классами, которые подвержены инкапсуляции. Данное условие просто условно, вы по прежнему сможете получить данные из этой ячейки из вне класса, но компилятор вас предупредит в этом и данные элементы не будут попадать в списке предлагаемых функций/переменных, при написании кода, что облегчает другим участникам работу с вашими библиотеками.
110 - Инициализируем окно программы, используя системные команды консоли.
111 - создаём объект нашего класса калькулятора
112 - реализуем завершение программы
Примечание: self.show() можно не использовать в самом классе, можно написать widget.show()
На этом всё! Спасибо, что дочитали до конца. Калькулятор вышел всего в 113 строк. Но прошу заметить, данный калькулятор не может вводить float числа, так как LCD display не может нормально отрисовать точку отдельно от 0. Вскоре я реализую такую возможность, а так же покажу, как работать с CSS, чтобы придать калькулятору красивый вид. Так же на подходе статья про приложение "заметки" с работой в фоновом режиме. Следите за новостями, до скорого!