Pyqt5 로 Window Application 개발

리기지·2024년 2월 1일

안녕하세요?

Pyqt5 를 약 2년간 사용해 개발 및 배포를 하였고 이를 남기고자 작성해 봅니다

목차

1 ) 라이센스
2 ) 사용법
3 ) 배포(빌드)
4 ) 후기

  1. 라이센스
    Pyqt5 는 라이센스는 2가지가 있는데요
  • 산업용 라이센스 : 비용을 지불하고 사용 (비용이 만만치 않습니다)
  • 오픈소스 라이센스 : GPL-3.0 로 배포시 소스를 공개하여 배포를 해야합니다

개인적인 목적으로 개발을 하신다면 Pyqt5 는 훌륭한 Python Gui 프래임 워크 입니다
하지만 회사에서 사용하게 될 경우 문제의 소지가 될 수 있습니다
라이센스 비용의 경우 Qt(라이센스) + Riverbank(라이센스) 2개를 구매해서 사용해야 하는걸로 알고 있습니다
*https://riverbankcomputing.com/commercial/license-faq

따라서 개발시 개인용으로 사용하시는걸 추천드립니다
만약 상업적으로 사용하시려면 Pyside6 를 추천드립니다(코드가 95 % 정도 비슷해요)

  1. 사용법
    (IDE 및 환경설정이 완료되었다고 생각하겠습니다)

Install

pip install Pyqt5
pip install Pyinstaller

main.py

from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (QMessageBox, QHBoxLayout, QVBoxLayout, QTableWidgetItem, QTableWidget, QListWidget,
                             QListWidgetItem, QWidget, QPushButton, QStyleFactory,
                             QApplication, QTabWidget, QCheckBox, QComboBox, QLabel, QLineEdit, QTextEdit, QGridLayout,
                             QProgressBar, QDesktopWidget, QFileDialog, QMainWindow)
from PyQt5.QtGui import QIcon

PyQt5.QtCore 는 Signal 또는 Slot 및 Enum 등을 다루는 package 입니다
PyQt5.QtWidgets 의 경우는 여러 위젯을 다루는 package 입니다
PyQt5.QtGui 의 경우 GUI 관련된 내용이 있습니다

특별한 경우가 아니면 위에 3가지 사용하게 됩니다
만약 그래프 또는 Data visualization 이 필요한 경우는

Qt.Charts
Qt.DataVisualization 를 사용하면 됩니다

사용예제 코드는 다음과 같습니다

from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (QMessageBox, QHBoxLayout, QVBoxLayout, QTableWidgetItem, QTableWidget, QListWidget,
                             QListWidgetItem, QWidget, QPushButton, QStyleFactory,
                             QApplication, QTabWidget, QCheckBox, QComboBox, QLabel, QLineEdit, QTextEdit, QGridLayout,
                             QProgressBar, QDesktopWidget, QFileDialog, QMainWindow)
from PyQt5.QtGui import QIcon



class MainForm(QMainWindow):

    def __init__(self):
        super().__init__()
        pos_x = QDesktopWidget().availableGeometry().center().x() - 450
        pos_y = QDesktopWidget().availableGeometry().center().y() - 200
        self.setGeometry(pos_x, pos_y, 900, 400)  # 중앙으로 이동
        self.setWindowTitle('Excel to Word : Freedom is not free [ Version 1.0.0 ]')

        self.CentralWidget = QWidget(self)
        self.CentralWidget.setGeometry(0, 0, 700, 400)
        # 전체 프레임
        self.QH_Main = QHBoxLayout(self.CentralWidget)

        # 왼쪽 프레임
        self.QV_Main_Inner_Left = QVBoxLayout()
        self.Btn_Home = QPushButton("Home")
        self.Btn_Home.clicked.connect(self.Show_Home)
        self.Btn_Preference = QPushButton("설정")

        self.Btn_Preference.clicked.connect(self.Show_Preference)
        self.QV_Main_Inner_Left.addWidget(self.Btn_Home, 1)
        self.QV_Main_Inner_Left.addWidget(self.Btn_Preference, 1)
        self.QV_Main_Inner_Left.addStretch(8)

        self.Tab_Widget = QTabWidget()
        self.Tab_Widget.tabBar().hide()
        self.QH_Main_Inner_Right = QHBoxLayout()

        self.Tab_Widget.addTab(Mid_Component(self), "Home")
        self.Tab_Widget.addTab(Preference_Component(self), "환경설정")

        self.QH_Main_Inner_Right.addWidget(self.Tab_Widget)

        self.QH_Main.addLayout(self.QV_Main_Inner_Left, 1)
        self.QH_Main.addLayout(self.QH_Main_Inner_Right, 8)

        # 변수 설정
        self.Text_Preamble = ""
        self.Line_Table_Bottom = ""
        self.Line_Table_Item_Digit = ""

        self.Read_Ini_File()

        self.setCentralWidget(self.CentralWidget)
        
        
def resource_path(relative_path):
    """ Get absolute path to resource, works for dev and for PyInstaller """
    base_path = getattr(sys, '_MEIPASS', os_path.dirname(os_path.abspath(__file__)))
    return os_path.join(base_path, relative_path)

def my_exception_hook(exctype, value, traceback):
    # Print the error and traceback
    print(exctype, value, traceback)
    # Call the normal Exception hook after
    sys._excepthook(exctype, value, traceback)
    # sys.exit(1)




if __name__ == '__main__':
    app = QApplication(sys.argv)
    # app.setStyle(QStyleFactory.create('CleanLooks'))
    MainForm = MainForm()
    temp_path_icon = resource_path('icon.ico')
    temp_path_css = resource_path('QT_Style.css')
    MainForm.Apply_Css_Style(temp_path_css)
    MainForm.show()
    # Exception hook
    
    app.setWindowIcon(QIcon(temp_path_icon))
    MainForm.setWindowIcon(QIcon(temp_path_icon))
    sys._excepthook = sys.excepthook
    sys.excepthook = my_exception_hook
    sys.exit(app.exec_())

*일부 코드가 생략되어 있습니다

이런식으로 작성하시면 됩니다

my_exception_hook 함수는 프로세스가 에러가 발생하였을 경우 callback 을 지정하였고

resource_path 의 경우는 개발 환경 및 배포시 불러와야 할 파일을 동적으로 찾는 함수 입니다
이렇게 하지 않을 경우 배포시에 이미지를 못 불러올 수 있습니다

  1. 배포(빌드)
    빌드의 경우 Pyinstaller 를 사용하여 배포 하시거나 다른 배포 라이브러리를 사용할 수 있습니다
    저의 경우는 Pyinstaller 를 사용하겠습니다

우선 Pyinstaller 를 설치하시게 되면

개발환경 폴더에 Scripts 폴더 안에 보시면 Pyinstaller.exe 파일이 있습니다

Pyinstaller.exe 에 인자를 주어 빌드를 수행하게 됩니다
(*인자는 공식문서 참조)

만약 Pycharm 을 사용하시면
External Tools 에 Pyinstaller 를 등록하게 되면 편하게 빌드 할 수 있습니다

저 같은 경우 이런식으로 저장해서 사용합니다
(직접 실행해서 사용해도 됩니다)

만약 처음 빌드 하시게 되면
.spec 파일이 생성되게 됩니다

# -*- mode: python ; coding: utf-8 -*-
# datas=[("C:\\Python_Word\\Lib\\site-packages\\docx\\templates", "docx\\templates")]

block_cipher = None


a = Analysis(['C:\\Python_Word\\ExcelWord.py'],
             pathex=['_Dill','C:\\Python_Word'],
             binaries=[],
             datas=[("C:\\Python_Word\\Lib\\site-packages\\docx\\templates", "docx\\templates")],
             hiddenimports=['docx', 'docx.shared'],
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher,
             noarchive=False)
a.datas +=[('icon.png', 'C:\\Python_Word\\icon.png', 'DATA'),('QT_Style.css', 'C:\\Python_Word\\QT_Style.css', 'DATA')]
pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher)
exe = EXE(pyz,
          a.scripts,
          a.binaries,
          a.zipfiles,
          a.datas,
          [],
          name='ExcelWord',
          debug=False,
          bootloader_ignore_signals=False,
          strip=False,
          upx=True,
          upx_exclude=[],
          runtime_tmpdir=None,
          console=False,
          icon='C:\\Python_Word\\icon.ico')

.Spec 파일을 보시면

C:\Python_Word\ExcelWord.py 는 entry point 로 처음 실행되는 파이썬 파일 입니다
pathex 의 경우 라이브러리를 찾을 때 추가로 찾을 폴더 위치 입니다
빌드시 사용된 라이브러리를 packing 하는데 이때 라이브러리를 못 찾는 경우가 있습니다
그럴 경우 추가로 지정해주시면 됩니다

또한 hiddenimports 의 경우 사용자가 직접 라이브러리를 packing 할 수 있습니다

a.datas +=[('icon.png', 'C:\\Python_Word\\icon.png', 'DATA'),('QT_Style.css', 'C:\\Python_Word\\QT_Style.css', 'DATA')]

이 코드는 배포시 이미지 파일 및 기타 파일을 packing 할 수 있습니다
저 같은 경우 ico 파일과 css 파일을 별도로 지정하여 같이 packing 합니다

만약 배포시 dll 파일을 못찾는 경우가 있으면

_Dill 파일 내부

를 추가로 지정해주시면 됩니다(.spec 파일 내용 참조)

이렇게 빌드 하게 되면 dist 폴더에 .exe 파일이 생성되게 됩니다

  1. 후기
    Pyqt5 를 사용해서 Window Application 을 개발하면서 느낀점은 사용에 익숙해 지시면 상당히 편하게 개발하실수 있습니다
    또한 Excel, Word, 크롤링등 다양한 프로그램을 개발할 수 있습니다
    만약 CSS 까지 잘하시면 상당히 완성도가 높은 프로그램을 개발할 수 있습니다

추가로 Pyside6 금방 사용할 수 있는 장점이 있습니다
다만 라이센스 문제가 있어 Pyside6 를 추천해 드립니다

2년간 개발하면 Pyqt5 는 충분히 배워볼만한 framework 였다고 생각합니다
다만 아쉬운점은 GUI 라이브러리가 제조사에 의해서만 제공되기 때문에 아쉬운 점이 있습니다
라이센스 문제가 없었더라면 상당히 인기 있는 python GUI framework 라고 생각 됩니다

감사합니다

0개의 댓글