python

Python + Pyside2 или просто «Калькулятор»

  • среда, 3 июля 2019 г. в 00:18:14
https://habr.com/ru/post/458536/
  • Python
  • Программирование
  • Разработка под Windows


Привет, Хабр!

Меня зовут Саша. Я Junior разработчик. Работаю тестировщиком ПО. В основном я пишу тесты при помощи Python+Selenium, но Python стал настолько интересен, что мне захотелось углубиться в него и выучить как можно больше фреймворков! Я захотел написать десктопное приложение, аля простой «Калькулятор». Мой выбор пал на Pyside2. Я не претендую на идеальный код или урок. Просто есть желание поделиться опытом, если кто-то, как и я, хочет начать шарить в Python. Если кому-то помогу — результата я достиг.

Начнем!

Писал свой код я в IDE от JetBrains «PyCharm». ОС — Windows.

Установка PySide2:

pip install PySide2

Там, где у вас корневая папка Python, переходим в нее, потом в папку «Lib» -> «site-packages» -> «Pyside2». У вас будет программа designer — это программа QtDesigner, которая позволит вам сделать собственный интерфейс вашей программы. Стоит заметить, что когда вы создадите свой файл в директории вашего проекта, то у него будет формат .ui, что понятное дело Python не поймет, поэтому нам нужно будет преобразоваться его в формат .py, но это позже.

Сначала создаем свою форму.

image

Делаем свой дизайн, называем кнопки справа в подразделе «Инспектор Объектов». Стоит сказать, что QtDesigner поддерживает каскадную таблицу стилей, а если легче, то найдя в свойствах параметр «styleSheet», у вас есть возможность сделать свой дизайн, опираясь на знания CSS.

image

Далее нам нужно преобразоваться наш файл .ui в формат, что бы его понимал Python. Переходим в командную строку и пишем

pyside2-uic "you_file.ui" -o "your_file.py"

Что делает команда «pyside2-uic»? Она преобразует ваш файл формата .ui в питонячий файл формата .py и создает из него класс Python. Возможно знающие люди скажут, что ui файл можно подключить и без конвертации в проект, но я буду аккуратен и лучше сделаю, как написано в мануалах по Pyside2.

Далее переходим к коду.

Открываем PyCharm, нашу директорию с проектом и создаем файл «calc_ui.py» или «something_ui.py» в зависимости от того, какую программу вы делайте. Приставка _ui В конце файла поможет нам не запутаться в файлах. В общем виде, должно выглядеть так:

image

Начнем с редактирования файла, который мы преобразовали из .ui в .py.

Внизу ищем данный код и копируем его, после удаляем из этого файла.

if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    Form = QtGui.QWidget()
    ui = Ui_Form()
    ui.setupUi(Form)
    Form.show()
    sys.exit(app.exec_())

Создаем наш основной файл, где будет прописана логика программы. Я назвал его, как, не удивительно «calc.py» (выше на скрине директории это видно).

Вставляем туда наш скопированный код и начинаем редактировать его.

import sys кидаем в начало, так как это правило хорошего тона.

Импортируем пару необходимых модулей для работы с нашей формой и из нашего файла «calc_ui.py» импортируем основной класс Ui_MainWindow.

Далее редактируем if __name__. Удаляем все не нужное. У вас должно получиться вот так:

if __name__ == '__main__':
    # Новый экземпляр QApplication
    app = QtWidgets.QApplication(sys.argv)
    # Сздание инстанса класса Калькулятор, который мы создадим далее
    calc = Calculator()
    # Запуск
    sys.exit(app.exec_())

Код я прокомментировал надеюсь внятно. Перейдем к созданию класса Calculator.

class Calculator(QtWidgets.QMainWindow, Ui_MainWindow):
    # Конструктор класса
    def __init__(self):
        super().__init__()
        # Создание формы и Ui (наш дизайн)
        self.setupUi(self)
        # Показать наше окно
        self.show()

Далее наша задача стоит так, что бы что-то происходило, когда мы тыкаем на кнопки «1», «2», «3» и т.д.

Там же в конструкторе класса объявляем подключение кнопки к какой-либо функции:

        # pressed
        self.pushButton.clicked.connect(self.digit_pressed)  # 1
        self.pushButton_2.clicked.connect(self.digit_pressed)  # 2
        self.pushButton_3.clicked.connect(self.digit_pressed)  # 3
        self.pushButton_4.clicked.connect(self.digit_pressed)  # 4
        self.pushButton_5.clicked.connect(self.digit_pressed)  # 5
        self.pushButton_6.clicked.connect(self.digit_pressed)  # 6
        self.pushButton_7.clicked.connect(self.digit_pressed)  # 7
        self.pushButton_8.clicked.connect(self.digit_pressed)  # 8
        self.pushButton_9.clicked.connect(self.digit_pressed)  # 9
        self.pushButton_10.clicked.connect(self.digit_pressed)  # 0
        self.pushButton_add.clicked.connect(self.pressed_equal)  # +
        self.pushButton_ded.clicked.connect(self.pressed_equal)  # -
        self.pushButton_div.clicked.connect(self.pressed_equal)  # /
        self.pushButton_mul.clicked.connect(self.pressed_equal)  # *
        self.pushButton_exp.clicked.connect(self.pressed_equal)  # **
        self.pushButton_log.clicked.connect(self.pressed_equal)  # log
        self.pushButton_procent.clicked.connect(self.pressed_equal)  # %
        self.pushButton_ENTER.clicked.connect(self.function_result)  # =
        self.pushButton_C.clicked.connect(self.function_clear)  # C
        self.pushButton_point.clicked.connect(self.make_fractional)  # .
        self.pushButton_delete.clicked.connect(self.function_delete)  # <
        self.pushButton_open_skob.clicked.connect(self.create_big_example)  # (

В коде на кнопках уже указана функция self.digit_pressed, давайте взглянем на нее, что же она делает, если пользователь нажал на кнопку с цифрами:

    # lineEdit - белое поле, в котором будут транслироваться все цифры и операции
    # text() - возвращает текст, который написан на нашей кнопке
    # setText() - кладет текст в объект от которого мы вызываем его
    # sender() - функция, которая возвращает отправителя сигнала (какая кнопка была нажата, от какой идет сигнал)
    def digit_pressed(self):
        button = self.sender()
        if self.lineEdit.text() == '0':
            # Если у нас в нашем поле результата "0", то заменяем его на текст, который написан на кнопке
            self.lineEdit.setText(button.text())
        else:
            if self.result == self.lineEdit.text():
                self.lineEdit.setText(button.text())
            else:
                self.lineEdit.setText(self.lineEdit.text() + button.text())
        self.result = 0

Комментарии к коду так же присутствуют.

Теперь рассмотрим функцию, которая будет реагировать на нажатие операций "+", "-" и т.д

    clear() - отчищает строку, которую от которой мы его вызываем
    def pressed_equal(self):
        button = self.sender()
        self.first_value = float(self.lineEdit.text())
        self.lineEdit.clear()
        self.label.setText(str(self.first_value) + button.text())
        self.equal = button.text()

Мы записываем первое значение в переменную self.first_value и чистим наше поле, для того, что бы ввели значение операции, после чего выводим в lineEdit(наше основное поле ввода и результата) все вместе с числом и операцией.

Почему float? Могу ответить так, что я решил все сделать float, а при выводе значения в блок результата, если результат имеет в конце ".0", удалять эту часть, что бы число было целочисленное. Лучше я не придумал.

У нас есть теперь функции для нажатия цифр и для нажатия операций, что дальше? Дальше нам надо выводить результат, это кнопка = (ENTER).

    def function_result(self):
        if self.equal == '+':
            self.function_addition()
        elif self.equal == '-':
            self.function_subtraction()
        elif self.equal == "/":
            self.function_divison()
        elif self.equal == '*':
            self.function_multiply()
        elif self.equal == "^":
            self.exponentiation()
        elif self.equal == "%":
            self.function_percent()
        elif self.equal == "log":
            self.function_log()

Напомню, что переменная self.first_value сейчас ровна первой переменной, которую мы вводим, а self.equal держит в себе операцию, которую мы нажали. После мы вводим второе число и жмем на =, мы пытаемся узнать, что же это за операция, а потом и определяем 2-ю переменную.

Переходим дальше, к функциям операций. Получилось у меня вот так:

    def function_addition(self):
        self.determinate_second_value()
        self.result = float(self.first_value + self.second_value)
        self.form_result()

    def function_subtraction(self):
        self.determinate_second_value()
        self.result = float(self.first_value - self.second_value)
        self.form_result()

    def function_divison(self):
        self.determinate_second_value()
        self.result = float(self.first_value / self.second_value)
        self.form_result()

    def function_multiply(self):
        self.determinate_second_value()
        self.result = float(self.first_value * self.second_value)
        self.form_result()

    def function_exponentiation(self):
        self.determinate_second_value()
        self.result = float(self.first_value ** self.second_value)
        self.form_result()

    def function_percent(self):
        self.determinate_second_value()
        self.result = float(self.first_value * (self.second_value / 100))
        self.form_result()

    def function_log(self):
        self.determinate_second_value()
        self.result = float(math.log(self.first_value, self.second_value))
        self.form_result()

Функция self.determinate_second_value() определяет второе значение переменной, которое мы ввели. Немного не логично и криво, но как есть, постараюсь учесть все ошибки потом в комментариях, когда умные люди скажут как правильно.

Немного освежимся.

  • self.first_value — имеет значение первого введенного числа
  • self.equal — имеет str переменную операции, которую мы нажали
  • self.second_value — имеет значение второй переменной

Далее в каждой из функции операций мы вызываем self.form_result(), которая, как не странно, формирует наш результат. Результат мы держим в переменной self.result.

    def form_result(self):
        self.result = str(self.result)
        if self.result[-2:] == '.0':
            self.result = self.result[:-2]
        self.lineEdit.setText(str(self.result))
        self.label.clear()

Поясню за self.result[-2:]. [-2:] говорит о том, что мы сравниваем последние 2 символа строки с ".0".

Вот и наш результат выводится в главный блок lineEdit, поздравляю.

Так же приложу здесь код, который удаляет один символ из строки или полностью все число или добавляет "." (точку) для создания дробного числа:

    def make_fractional(self):
        value = self.lineEdit.text()
        if '.' not in value:
            self.lineEdit.setText(value + '.')

    def function_delete(self):
        value = self.lineEdit.text()
        self.lineEdit.setText(value[:-1])
        
    def function_clear(self):
        self.lineEdit.setText('0')

Весь код под спойлером:

Весь код
from PySide2 import QtWidgets
from calc_ui import Ui_MainWindow
import sys
import math

class Calculator(QtWidgets.QMainWindow, Ui_MainWindow):
    def __init__(self):
        super().__init__()
        # Создание формы и Ui (наш дизайн)
        self.setupUi(self)
        self.show()
        self.lineEdit.setText('0')

        self.first_value = None
        self.second_value = None
        self.result = None
        self.example = ""
        self.equal = ""

        # pressed
        self.pushButton.clicked.connect(self.digit_pressed)  # 1
        self.pushButton_2.clicked.connect(self.digit_pressed)  # 2
        self.pushButton_3.clicked.connect(self.digit_pressed)  # 3
        self.pushButton_4.clicked.connect(self.digit_pressed)  # 4
        self.pushButton_5.clicked.connect(self.digit_pressed)  # 5
        self.pushButton_6.clicked.connect(self.digit_pressed)  # 6
        self.pushButton_7.clicked.connect(self.digit_pressed)  # 7
        self.pushButton_8.clicked.connect(self.digit_pressed)  # 8
        self.pushButton_9.clicked.connect(self.digit_pressed)  # 9
        self.pushButton_10.clicked.connect(self.digit_pressed)  # 0
        self.pushButton_add.clicked.connect(self.pressed_equal)  # +
        self.pushButton_ded.clicked.connect(self.pressed_equal)  # -
        self.pushButton_div.clicked.connect(self.pressed_equal)  # /
        self.pushButton_mul.clicked.connect(self.pressed_equal)  # *
        self.pushButton_exp.clicked.connect(self.pressed_equal)  # **
        self.pushButton_log.clicked.connect(self.pressed_equal)  # log
        self.pushButton_procent.clicked.connect(self.pressed_equal)  # %
        self.pushButton_ENTER.clicked.connect(self.function_result)  # =
        self.pushButton_C.clicked.connect(self.function_clear)  # C
        self.pushButton_point.clicked.connect(self.make_fractional)  # .
        self.pushButton_delete.clicked.connect(self.function_delete)  # <
        self.pushButton_open_skob.clicked.connect(self.create_big_example)  # (

    def digit_pressed(self):
        # sender - функция, которая возвращает отправителя сигнала (какая кнопка была нажата, от какой идет сигнал)
        button = self.sender()
        if self.lineEdit.text() == '0':
            self.lineEdit.setText(button.text())
        else:
            if self.result == self.lineEdit.text():
                self.lineEdit.setText(button.text())
            else:
                self.lineEdit.setText(self.lineEdit.text() + button.text())
        self.result = 0

    def form_result(self):
        self.result = str(self.result)
        if self.result[-2:] == '.0':
            self.result = self.result[:-2]
        self.lineEdit.setText(str(self.result))
        self.label.clear()

    def make_fractional(self):
        value = self.lineEdit.text()
        if '.' not in value:
            self.lineEdit.setText(value + '.')

    def function_delete(self):
        value = self.lineEdit.text()
        self.lineEdit.setText(value[:-1])

    def function_clear(self):
        self.lineEdit.setText('0')

    def pressed_equal(self):
        button = self.sender()
        self.first_value = float(self.lineEdit.text())
        self.lineEdit.clear()
        self.label.setText(str(self.first_value) + button.text())
        self.equal = button.text()

    def function_addition(self):
        self.determinate_second_value()
        self.result = float(self.first_value + self.second_value)
        self.form_result()

    def function_subtraction(self):
        self.determinate_second_value()
        self.result = float(self.first_value - self.second_value)
        self.form_result()

    def function_divison(self):
        self.determinate_second_value()
        self.result = float(self.first_value / self.second_value)
        self.form_result()

    def function_multiply(self):
        self.determinate_second_value()
        self.result = float(self.first_value * self.second_value)
        self.form_result()

    def function_exponentiation(self):
        self.determinate_second_value()
        self.result = float(self.first_value ** self.second_value)
        self.form_result()

    def function_percent(self):
        self.determinate_second_value()
        self.result = float(self.first_value * (self.second_value / 100))
        self.form_result()

    def function_log(self):
        self.determinate_second_value()
        self.result = float(math.log(self.first_value, self.second_value))
        self.form_result()

    def determinate_second_value(self):
        self.second_value = float(self.lineEdit.text())
        self.lineEdit.clear()
        self.label.setText(str(self.first_value) + self.equal + str(self.second_value))

    def function_result(self):
        if self.equal == '+':
            self.function_addition()
        elif self.equal == '-':
            self.function_subtraction()
        elif self.equal == "/":
            self.function_divison()
        elif self.equal == '*':
            self.function_multiply()
        elif self.equal == "^":
            self.exponentiation()
        elif self.equal == "%":
            self.function_percent()
        elif self.equal == "log":
            self.function_log()

if __name__ == '__main__':
    # Новый экземпляр QApplication
    app = QtWidgets.QApplication(sys.argv)
    # Сздание инстанса класса
    calc = Calculator()
    # Запуск
    sys.exit(app.exec_())


Да, калькулятор не вычисляет большие функции и выражения, я над этим работаю!

Спасибо вам большое за внимание. Удачи вам и развивайтесь! Это круто.

Так же хотелось бы пригласить вас в свой Telegram-канал JuniorProger, где я рассказываю о жизни Junior программиста и его тернистый, но очень интересный путь становления It-специалистом.