Приветствую друзья!
В данной статье напишем шифратор/дешифратор Цезаря на Python и прикрутим к нему GUI.
По окончанию работ наше приложение будет выглядеть так:
ОС - WIndows 10. Python 3.7+, я использую 3.11.2 (последний на момент написания статьи)
Писать проект будем в PyCharm
Дизайнить интерфейс в QtDesigner
Свяжем интерфейс с нашим python проектом с помощью PySide6
PySide6 — привязка языка Python к инструментарию Qt, совместимая на уровне API с PyQt. PySide6 доступна для свободного использования как в открытых, так и закрытых, в частности, коммерческих проектах, поскольку лицензирована по LGPL.
Итак, установим необходимые инструменты:
Ссылка скрыта от гостей
Ссылка скрыта от гостей
В PyCharm создадим новый проект File -> New Project, я назвал ROT_decoder_GUI
Внутри проекта создадим пару файлов: main.py и cipher_functions.py
Настройки интерпретатора стандартные, переходим в терминал (Alt + F12) и устанавливаем PySide6
pip install pyside6
После установки переходим в директорию ~\PycharmProjects\ROT_decoder_GUI\venv\Lib\site-packages\PySide6\
Там находятся необходимые бинарники для дальнейшей разработки включая designer.exe
Главное окно программы:
Создадим новую форму Файл -> Новый, выберем Main Window
В инспекторе объектов удаляем menubar и statusbar.
Кидаем на форму объекты Layout - 2 вертикальных и 3 горизонтальных. В дальнейшем на них будем накидывать наши элементы.
Устанавливаем необходимые объекты, в скобочках напишу.
1. Ввод текста для шифрования/дешифрования (label, Text Edit)
2. Вывод расшифрованного/ зашифрованного текста (label, Text Edit)
3. Кнопки (Push Button - 3 штуки)
4. Слайдер и спинбокс (Label, Horizontal Slider и Spin Box)
5. Label об авторе, ссылки, год, копирайт и т.д. (label)
Двойным кликом в инспекторе даем осмысленные имена.
В инспекторе выделяем объект central widget и кнопкой на панели инструментов компонуем все по вертикали.
Приступим к стилизации наших объектов. В инспекторе ПКМ по Main WIndows -> Изменить Style Sheet..
Имеется поддержка CSS, что лично для меня очень удобно, добавляем наш код:
CSS:
QTextEdit {
border: 2px solid gray;
border-radius: 5px;
}
QPushButton {
border: 2px solid gray;
border-radius: 5px;
font: 700 12pt "Hack";
}
QPushButton:hover {
border-color: #090;
}
QPushButton:pressed {
border: 4px solid #090;
border-radius: 5px;
}
QPushButton:checked {
color: white;
background-color: #090;
border: 2px solid black;
border-radius: 5px;
}
QSlider::sub-page:horizontal {
background-color: #090;
}
QSlider::add-page:horizontal {
background-color: gray;
}
QSlider::handle:horizontal {
background-color: black;
width: 16px;
border-radius:8px;
margin-top: -5px;
margin-bottom: -5px;
}
QSlider::groove:horizontal {
background-color: transparent;
height: 7px;
}
QLabel {
font: 500 11pt "Ubuntu";
}
QSpinBox {
border: 2px solid gray;
border-radius: 5px;
background: transparent;
padding: 2px;
}
QSpinBox:hover {
border-color: #009900;
}
Название классов элементов также указано в инспекторе объектов.
В редакторе свойств элементов, уберем у спинбокса кнопки, кнопкам Decrypt и Encrypt (элемент Push Button) установим свойство Checkable, у Decrypt поставим Checked.
Слайдеру и спинбоксу поставим шаг и значение по умолчанию, у меня 13 (ROT 13)
Осталось разобраться с ресурсами и добавить наши иконки. Я взял иконки из material design от гугла.
С различными свойствами объектов можете поиграться сами (размер, курсор, шрифт и т.д.) Настроек много.
Превью интерфейса смотрим сочетанием Ctrl + R
Проектирование и прототипирование закончено, можно приступить к коду
В файл cipher_functions.py записываем собственно сами функции шифрования/дешифрования по Цезарю.
Python:
upper_english_alp = {key: chr(value) for key, value in zip(range(26), range(65, 91))}
lower_english_alp = {key: chr(value) for key, value in zip(range(26), range(97, 123))}
upper_russian_alp = {key: chr(value) for key, value in zip(range(32), range(1040, 1072))}
lower_russian_alp = {key: chr(value) for key, value in zip(range(32), range(1072, 1104))}
def encrypt(original_string: str, shift_key: int) -> str:
enc = ''
for i in original_string:
if i.isupper() and i.isalpha() and i in upper_english_alp.values():
enc += upper_english_alp.get(((ord(i) + shift_key - ord('A')) % 26))
elif i.lower() and i.isalpha() and i in lower_english_alp.values():
enc += lower_english_alp.get(((ord(i) + shift_key - ord('a')) % 26))
elif i.isupper() and i.isalpha() and i in upper_russian_alp.values():
enc += upper_russian_alp.get(((ord(i) + shift_key - ord('А')) % 32))
elif i.lower() and i.isalpha() and i in lower_russian_alp.values():
enc += lower_russian_alp.get(((ord(i) + shift_key - ord('а')) % 32))
else:
enc += i
return enc
def decrypt(encrypted_string: str, shift_key: int) -> tuple[str, int, int]:
dec = ''
ru = 0
en = 0
for i in encrypted_string:
if i.isupper() and i.isalpha() and i in upper_english_alp.values():
dec += upper_english_alp.get(((ord(i) - shift_key - ord('A')) % 26))
en += 1
elif i.lower() and i.isalpha() and i in lower_english_alp.values():
dec += lower_english_alp.get(((ord(i) - shift_key - ord('a')) % 26))
en += 1
elif i.isupper() and i.isalpha() and i in upper_russian_alp.values():
dec += upper_russian_alp.get(((ord(i) - shift_key - ord('А')) % 32))
ru += 1
elif i.lower() and i.isalpha() and i in lower_russian_alp.values():
dec += lower_russian_alp.get(((ord(i) - shift_key - ord('а')) % 32))
ru += 1
else:
dec += i
return dec, ru, en
С помощью генератора словарей создаем 4 словаря (2 для английского и 2 для русского алфавитов, lower и upper case)
Ключами выступают числа от 0 до длина алфавита, а значениями сами буквы алфавита.
Сами функции отличаются только тем, что при шифровании мы прибавляем шаг(ROT key) и при расшифровке отнимаем.
Функция encrypt возвращает строку -> str, а decrypt кортеж -> tuple[str, int, int]
В decrypt я считаю количество символов русского и английского алфавитов, потом использую это в функции bruteforce.
Ищем остаток от деления для англ алфавита 26, для нашего 32!, как оказалось в юникоде Ё идет НЕ после Е
Можно докрутить, словарь сделать с Ё или заменять ее на Е. В данный момент это не принципиально.
Проводим некоторые проверки
if i.isupper() and i.isalpha() and i in upper_english_alp.values()
Регистр, буква ли это и принадлежность к алфавиту. Сделано для того чтобы приложение не трогало цифры, спец. символы, пробелы и т.д.
Теперь нам надо связать интерфейс с приложением.
Каркас приложения - main.py:
Python:
import sys
from cipher_functions import decrypt, encrypt
from PySide6.QtWidgets import QApplication, QMainWindow, QMessageBox
from design import Ui_MainWindow
class RotDecoder(QMainWindow):
def __init__(self):
super(RotDecoder, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = RotDecoder()
window.show()
sys.exit(app.exec())
Есть пару вариантов превратить наш design.ui в design.py
1. Из Qt designer - Форма -> Показать код Python, далее копируем или сохраняем в design.py
2. В PyCharm Alt + F12
pyside6-uic .\design.ui -o .\design.py
Ресурсы -
pyside6-rcc .\resources.qrc -o resources.py
Ресурсы линкуются в файле design.py, проверяем правильность импорта:
import resources
Свяжем spinbox и slider по изменению значений, создадим функцию в классе RotDecoder файла main.py:
Python:
def connection_slider_spinbox(self) -> None:
self.ui.horizontalSlider.valueChanged.connect(self.ui.spinBox.setValue)
self.ui.spinBox.valueChanged.connect(self.ui.horizontalSlider.setValue)
Прописываем метод в конструкторе класса:
Python:
class RotDecoder(QMainWindow):
def __init__(self):
...
...
...
self.connection_slider_spinbox()
Добавим наш функционал:
Python:
def decrypting(self):
self.ui.textEdit_2.setText(decrypt(self.ui.textEdit.toPlainText(), self.ui.spinBox.value())[0])
def encrypting(self):
self.ui.textEdit_2.setText(encrypt(self.ui.textEdit.toPlainText(), self.ui.spinBox.value()))
Кнопки Decrypt и Encrypt не могут быть в одинаковом состоянии(checked), меняем при нажатии:
Python:
def checked_dec_btn(self):
self.ui.decrypt_btn.nextCheckState()
self.encrypting()
self.ui.spinBox.valueChanged.connect(self.encrypting)
self.ui.textEdit.textChanged.connect(self.encrypting)
def checked_enc_btn(self):
self.ui.encrypt_btn.nextCheckState()
self.decrypting()
self.ui.spinBox.valueChanged.connect(self.decrypting)
self.ui.textEdit.textChanged.connect(self.decrypting)
Кнопка BRUTEFORCE:
Python:
def bruteforce(self):
self.ui.textEdit_2.clear()
if decrypt(self.ui.textEdit.toPlainText(), 1)[1] > decrypt(self.ui.textEdit.toPlainText(), 1)[2]:
for i in range(1, 33):
ls = ['ROT key ' + str(i) + ': ' + decrypt(self.ui.textEdit.toPlainText(), i)[0] + '\n' for i in
range(1, 33)]
with open('bruted.txt', 'w', encoding='UTF-8') as file:
file.writelines(ls)
self.ui.textEdit_2.append(f'ROT key {i}:{decrypt(self.ui.textEdit.toPlainText(), i)[0]}')
else:
for i in range(1, 27):
ls = ['ROT key ' + str(i) + ': ' + decrypt(self.ui.textEdit.toPlainText(), i)[0] + '\n' for i in
range(1, 27)]
with open('bruted.txt', 'w', encoding='UTF-8') as file:
file.writelines(ls)
self.ui.textEdit_2.append(f'ROT key {i}: {decrypt(self.ui.textEdit.toPlainText(), i)[0]}')
Заполняем класс нашими методами:
Python:
class RotDecoder(QMainWindow):
def __init__(self):
super(RotDecoder, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.connection_slider_spinbox()
self.decrypting()
self.ui.spinBox.valueChanged.connect(self.decrypting)
self.ui.textEdit.textChanged.connect(self.decrypting)
self.ui.encrypt_btn.clicked.connect(self.checked_dec_btn)
self.ui.decrypt_btn.clicked.connect(self.checked_enc_btn)
self.ui.brute_btn.clicked.connect(self.bruteforce)
self.ui.actionExit.triggered.connect(sys.exit)
self.ui.actionabout.triggered.connect(self.about)
Сборка приложения под Windows:
Осталось превратить наш проект в исполняемый файл. Компиляцию можно делать разными способами, например PyInstaller или Nuitka.
Я выбрал второе, устанавливаем Nuitka:
pip install nuitka
Чтобы собрать все приложение в один единственный исполняемый EXE файл на понадобится библиотека zstandart:
pip install zstandard
Код:
nuitka --onefile --follow-imports --enable-plugin=pyside6 --windows-disable-console --windows-icon-from-ico=res\key.ico --remove-output -o caesar_cipher.exe main.py
--follow-imports - соблюдаем импорты
--enable-plugin=pyside6 - добавляем плагин PySide6
--windows-disable-console - убираем консольное окно
--windows-icon-from-ico=res\key.ico - ставим иконку из файла
--remove-output - после удаляем все папки генерируемые во время компиляции
Кстати говоря наше приложение получилось кроссплатформенным и при желание его можно собрать под MacOS или Linux!
Прикладываю файлы проекта и скомпилированный исходник. Вот так вот "просто" можно собрать GUI для приложения на Python!
Ссылка скрыта от гостей
Если что-то забыл указать, спрашивайте! Отвечу, исправлю.
За сием прощаюсь, до новых встреч!
Последнее редактирование модератором: