Найти в Дзене
НОВ

Создание калькулятора на PyQt5 (без qt designer)

Всем привет! В данной статье вы узнаете как создать свой собственный калькулятор на python с использованием большой библиотеки pyqt5.

Для начала, если у вас нет python, переходим на сайт: https://www.python.org/ и скачиваем версию 3.9. На данный момент имеется версия 3.10, но на ней не работает множество библиотек. Далее устанавливаем интересующую вас среду разработки. Я использую pycharm. Как по мне, самый удобный инструмент, при том, что имеется бесплатная лицензия, а платную лицензию можно получить бесплатно, выполняя курсы на платформе stepik. Далее устанавливаем pyqt5:

  1. прописываем в консоли: pip install pyqt5;
  2. Если вам понадобится в будущем русская версия 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) - устанавливает сетку состояний положений и видимостей объектов.

self.display = QLCDNumber(self) - создаёт цифровой дисплей.
self.display = QLCDNumber(self) - создаёт цифровой дисплей.

.setGeometry(10, 10, 260, 75) - задаёт положение и размер виджета.

Переходим к кнопкам:

-5

Далее пойдём описание по строкам:

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, чтобы придать калькулятору красивый вид. Так же на подходе статья про приложение "заметки" с работой в фоновом режиме. Следите за новостями, до скорого!