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, но это позже. 
Сначала создаем свою форму.
 
Делаем свой дизайн, называем кнопки справа в подразделе «Инспектор Объектов». Стоит сказать, что QtDesigner поддерживает каскадную таблицу стилей, а если легче, то найдя в свойствах параметр «styleSheet», у вас есть возможность сделать свой дизайн, опираясь на знания CSS.
 
Далее нам нужно преобразоваться наш файл .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 В конце файла поможет нам не запутаться в файлах. В общем виде, должно выглядеть так:
 
Начнем с редактирования файла, который мы преобразовали из 
.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-специалистом.