Калькуляторы – это одни из самых простых приложений, которые идут по умолчанию в каждой версии Windows. Со временем их расширял для поддержки научных и программных режимов, однако на фундаментальном уровне все они одинаковы.
В этой небольшой статье мы реализуем рабочий стандартный калькулятор при помощи PyQt5. Здесь используется логика из трех частей, включая короткий стек, оператор и состояние. Базовые операции с памятью также используются.
Есть вопросы по Python?
На нашем форуме вы можете задать любой вопрос и получить ответ от всего нашего сообщества!
Паблик VK
Одно из самых больших сообществ по Python в социальной сети ВК. Видео уроки и книги для вас!
Пока это реализовано под Qt, вы можете легко конвертировать логику в работу для работы в оборудовании при помощи MicroPython или Raspberry Pi.
Исходный код
Скачать: https://github.com/mfitzp/15-minute-apps/tree/master/calculator
Полный исходный код Calculon доступен в пятнадцатиминутном репозитории. Вы можете скачать\клонировать его для получения рабочей копии:
1 2 |
git clone git@github.com:mfitzp/15-minute-apps.git cd 15-minute-apps/calculator |
Затем установить все необходимые зависимости при помощи:
1 |
pip3 install -r requirements.txt |
После этого вы можете запустить калькулятор при помощи:
1 |
python3 notepad.py |
Ознакомьтесь с тем, как работает код в путеводителе.
Пользовательский интерфейс
Пользовательский интерфейс «Calculon» был создан в Qt Designer. Макет mainwindow использует QVBoxLayout с LCD экраном добавленным вверху и QGridLayout внизу.
Мы используем макет сетки, который используется для позиционирования всех кнопок калькулятора. Каждая кнопка занимает одно место в сетке, кроме знака равно, который занимает две клетки.
Каждая кнопка определена сочетанием горячих клавиш на клавиатуре для вызова сигнала .pressed. Например, 3 для кнопки 3. Действия в каждой кнопке определены кодом и связаны с этим сигналом.
Если вы хотите поправить дизайн в Qt Designer, не забудьте пересоздать файл MainWindow.py используя pyuic5 mainwindow.ui -o MainWindow.py.
Действия
Чтобы кнопки делали что-либо, нам нужно связать их с определенными обработчиками. Определенные связи показаны сначала внизу, затем подробно описанные обработчики.
Сначала мы подключаем цифровые кнопки к своим обработчикам. В Qt Designer мы назвали все кнопки, используя стандартный формат, так, в pushButton_nX литера Х является числом. Это упрощает их итерацию и подключение.
Мы используем оберточную функцию на сигнале для передачи дополнительной информации с каждым запуском. В нашем случае, введенное число от 0 до 9 используя функцию range.
1 2 |
for n in range(0, 10): getattr(self, 'pushButton_n%s' % n).pressed.connect(lambda v=n: self.input_number(v)) |
Следующий блок сигналов, который мы подключим, нужен для стандартных операций подсчетов, включая суммирование, умножение, вычитание и деление. Опять же, они подключены к общему слоту и состоят из завернутого сигнала для передачи операции (определенный тип операторов Python).
1 2 3 4 |
self.pushButton_add.pressed.connect(lambda: self.operation(operator.add)) self.pushButton_sub.pressed.connect(lambda: self.operation(operator.sub)) self.pushButton_mul.pressed.connect(lambda: self.operation(operator.mul)) self.pushButton_div.pressed.connect(lambda: self.operation(operator.truediv)) # operator.div для Python2.7 |
В дополнении к числам и операторам, у нас есть ряд пользовательских функций для подключения. К примеру, проценты (для преобразования введенного ранее числа для получения процента), знак равно, сброс и память.
1 2 3 4 5 6 7 |
self.pushButton_pc.pressed.connect(self.operation_pc) self.pushButton_eq.pressed.connect(self.equals) self.pushButton_ac.pressed.connect(self.reset) self.pushButton_m.pressed.connect(self.memory_store) self.pushButton_mr.pressed.connect(self.memory_recall) |
Теперь кнопки и действия связаны, и мы можем реализовать логику в методах слота для обработки этих действий.
Операции
Операции калькулятора обрабатываются с использованием трех компонентов – стек, состояние и нынешняя операция.
Стек
Стек – это небольшое хранилище памяти для двух элементов максимум, который содержит числовые значения, которые мы хотим посчитать. Когда пользователь начинает вводить новое число, оно попадает в конец стека (который является началом при условии, если стек является пустым). Каждое введенное число умножает текущий стек на 10 и добавляет введенное значение.
1 2 3 4 5 6 7 8 |
def input_number(self, v): if self.state == READY: self.state = INPUT self.stack[-1] = v else: self.stack[-1] = self.stack[-1] * 10 + v self.display() |
Таким образом мы заполняем числа в правой части ,как и ожидалось. Иными словами:
Состояние
Флажок состояние (state) нужен для переключения между готовыми и введенными состояниями. Это также влияет на поведение при вводе чисел. В режиме готовности, введенное значение настраивается напрямую под стек и текущую позицию. В режиме ввода, указанном выше, используется логика shift+add.
Это необходимо, так что мы можем вводить числа над результатом подсчета, вместо того, чтобы получить новые добавленные в результаты предыдущего подсчета числа.
1 2 3 4 5 6 7 8 |
def input_number(self, v): if self.state == READY: self.state = INPUT self.stack[-1] = v else: self.stack[-1] = self.stack[-1] * 10 + v self.display() |
Вы увидите переключатели между состояниями READY и INPUT.
Текущая операция (current_op)
Переменная current_op содержит текущую активную операцию, которая будет задействована после того, как пользователь нажмет на кнопку уравнения. Если операция в данный момент прогрессирует, то мы сначала подсчитываем результат этой операции, выводим результат в стек и создаем новую.
Запуск новой операции также вводит 0 в стек, таким образом, увеличивая его длину до 2, затем переключает режим на INPUT. Таким образом, мы получаем гарантию того, что любое вводимое далее число будет начинаться с нуля.
1 2 3 4 5 6 7 |
def operation(self, op): if self.current_op: # Завершение текущей операции self.equals() self.stack.append(0) self.state = INPUT self.current_op = op |
Обработчик операции для подсчета процентов работает немного иначе. Вместо этого, он управляет текущим содержимым стека напрямую. Вызов operation_pc берет последнее значение в стеке и делит его на 100.
1 2 3 4 |
def operation_pc(self): self.state = INPUT self.stack[-1] *= 0.01 self.display() |
Знак равно
Основа калькулятора – это обработчик, который действительно выполняет кое-какую математику. Все операции (за исключением процентов) обрабатываются обработчиком знака равно, который вызывается нажатием на клавишу знака равно, Enter или другой операционной клавиши.
Обработчик equals берет current_op и добавляет его к значениям в стеке (2 значения, распакованные при помощи *self.stack) для получения результата. Результат отправляется обратно в стек в качестве единственного значения, и мы переходим к состоянию READY. Ошибки (исключения, например, деление на ноль) учитываются, и уведомление об ошибке может быть отображено при необходимости.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
def equals(self): # Поддержка, позволяющая «=» повторять предыдущую операцию # если новые данные небыли введены. if self.state == READY and self.last_operation: s, self.current_op = self.last_operation self.stack.append(s) if self.current_op: self.last_operation = self.stack[-1], self.current_op try: self.stack = [self.current_op(*self.stack)] except Exception: self.lcdNumber.display('Err') self.stack = [0] else: self.current_op = None self.state = READY self.display() |
Была также добавлена поддержка для повторяемых предыдущих операций при повторном нажатии знака равно. Это может быть реализовано при сохранении значения и оператора, когда задействуется знак равно. Повторное использование возможно при условии, что знак равно введен еще раз без выхода из режима READY (без пользовательского введения).
Память
Наконец, мы можем определить обработчики для действий памяти. Для «Calculon» мы подобрали только два действия с памятью – хранение и повторный вызов. Хранение берет текущее значение с LCD дисплея и копирует его в self.memory. Повторный вызов берет значение из self.memory и помещает его в конечное место нашего стека.
1 2 3 4 5 6 7 |
def memory_store(self): self.memory = self.lcdNumber.value() def memory_recall(self): self.state = INPUT self.stack[-1] = self.memory self.display() |
Настроив режим на INPUT и обновив дисплей мы сможем действовать также, как при введении чисел от руки.
Дальнейшие идеи
Текущая версия «Calculon» поддерживает только базовые математические операции. Большая часть пользовательских калькуляторов также включает в себя поддержку научных (в некоторых случаях и программных) режимов, которые добавляют ряд альтернативных функций.
В «Calculon» вы можете определить эти дополнительные операции как набор лямбд, каждая из которых принимает два параметра для обработки.
Переключение режимов (например, между нормальным и научным) в калькуляторе может быть для нынешнего макета, базирующегося на QMainWindow. Вы можете перебрать макет калькулятора в QtDesigner, чтобы иметь возможность использовать базу QWidget. Каждое представление – это обычный виджет, и переключение между режимами может быть выполнено смены вашего центрального виджета в рабочем окне.
Являюсь администратором нескольких порталов по обучению языков программирования Python, Golang и Kotlin. В составе небольшой команды единомышленников, мы занимаемся популяризацией языков программирования на русскоязычную аудиторию. Большая часть статей была адаптирована нами на русский язык и распространяется бесплатно.
E-mail: vasile.buldumac@ati.utm.md
Образование
Universitatea Tehnică a Moldovei (utm.md)
- 2014 — 2018 Технический Университет Молдовы, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
- 2018 — 2020 Технический Университет Молдовы, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»