본문 바로가기
목차
Python

[Py] PyInstaller 사용하기-GUI App.

by ds31x 2025. 6. 8.
728x90
반응형

0. Prerquisites

다음을 읽어볼 것.

https://bme808.blogspot.com/2022/10/python-pyinstaller.html

 

Python : Pyinstaller

python 프로그램을 exe와 같은 실행파일로 만들어준다. 단, `conda`를 사용할 경우, 지나치게 파일사이즈가 커지는 문제가 있다. `numpy`의 MKㅣ 라이브러리 관련 문제인데... 연구개발용으로 보낼 때는

bme808.blogspot.com

 

가급적 Pyinstaller는 프로젝트 시작할 때마다 업데이트를 해주는게 좋음:

 

사용된 버전은 다음과 같음: update시 아래 둘 다 같이 해줄 것.

  • pyinstaller               6.14.0          py311h9272bb7_0    conda-forge
  • pyinstaller-hooks-contrib 2025.4             pyhd8ed1ab_0    conda-forge

 


1. source 코드 및 구조.

다음의 GUI App를 사용하여 PyInstaller의 사용법을 살펴본다.

코드는 다음과 같음:

# PySide6의 GUI 구성 요소들 import
from PySide6.QtWidgets import (
    QMainWindow,       # 메인 윈도우(타이틀바 포함 창 프레임)
    QApplication,      # 애플리케이션 객체 (이벤트 루프 관리)
    QWidget,           # 중앙 위젯 (다른 위젯을 담을 수 있음)
    QLabel,            # 텍스트 또는 이미지를 표시하는 라벨
    QPushButton,       # 클릭 가능한 버튼
    QVBoxLayout,       # 수직 레이아웃 관리자
)

# 아이콘(이미지)을 위한 클래스
from PySide6.QtGui import QIcon

# 정렬 등 다양한 상수들
from PySide6.QtCore import Qt

# 표준 파이썬 모듈
import sys  # 시스템 인자 및 종료 코드 등 관리
import os   # 파일 및 디렉토리 경로 처리용

# 메인 윈도우 클래스 정의 (QMainWindow를 상속받음)
class MW(QMainWindow):
    
    def __init__(self, base_dir):
        super().__init__()
        self.base_dir = base_dir  # 리소스 이미지 등의 기준 경로
        self.init_ui()            # 사용자 인터페이스 초기화
        
    def init_ui(self):
        self.setWindowTitle("Test PyInstaller")  # 창의 제목 설정
        
        # 수직 레이아웃 생성
        lm = QVBoxLayout()
        
        # 라벨 생성 및 가운데 정렬
        l = QLabel("PyInstaller Test App.")
        l.setAlignment(Qt.AlignCenter)
        lm.addWidget(l)  # 레이아웃에 라벨 추가
        
        # 닫기 버튼 생성
        btn_close = QPushButton("close")
        # 버튼에 아이콘 설정 (리소스 경로는 base_dir를 기준으로)
        btn_close.setIcon(QIcon(os.path.join(self.base_dir, "resources/rocket.png")))
        # 클릭 시 현재 윈도우 닫기
        btn_close.clicked.connect(self.close)
        lm.addWidget(btn_close)  # 레이아웃에 버튼 추가
        
        # 최소화 버튼 생성
        btn_min = QPushButton("minimize")
        btn_min.setIcon(QIcon(os.path.join(self.base_dir, "resources/star.png")))
        # 클릭 시 현재 윈도우 최소화
        btn_min.clicked.connect(self.lower)
        lm.addWidget(btn_min)
        
        # QWidget을 레이아웃 컨테이너로 설정
        container = QWidget()
        container.setLayout(lm)
        
        # 메인 윈도우의 중앙 위젯으로 설정
        self.setCentralWidget(container)
        
        # 윈도우 보여주기
        self.show()

# 프로그램 진입점
if __name__ == "__main__":
    # QApplication 객체 생성 (PyQt나 PySide에서는 반드시 하나만 존재해야 함)
    app = QApplication(sys.argv)
    
    # base_dir: 리소스(이미지 등) 파일의 기준 경로 설정
    # PyInstaller로 실행될 경우 임시 디렉토리(sys._MEIPASS)를 사용해야 함
    base_dir = ""
    if hasattr(sys, '_MEIPASS'):
        # PyInstaller가 실행 시 압축 해제한 리소스의 임시 디렉토리
        base_dir = sys._MEIPASS
    else:
        # 일반적인 실행 환경 (IDE 또는 파이썬 인터프리터)
        base_dir = os.path.dirname(__file__)
    
    # 메인 윈도우 인스턴스 생성
    wnd = MW(base_dir)
    
    # 앱 실행 (이벤트 루프 진입)
    sys.exit(app.exec())

 

관련된 이미지 파일 및 icon파일은 다음의 구조로 배치됨.

❯ tree
.
├── app.py
├── pet.icns
└── resources
    ├── rocket.png
    └── star.png

2 directories, 4 files

 

다음의 압축파일을 풀면 packaging이라는 디렉토리가 생기고 위의 구조를 가짐.

packaging.zip
1.48MB

 

참고로 사용한 아이콘은 다음의 분이 만든 것임 - 채워진 스타 작가: Icons8


2. pyinstaller 로 GUI App용 executable file 만들기

위의 소스파일로 pyinstaller로 gui app.용 executable file을 만들려면 다음의 방식이 가장 기본임.

pyinstaller --windowed app.py

 

이 명령을 수행하면, builddist 디렉토리와 app.spec 파일이 만들어짐.

 

다음이 결과임:

더보기
❯ tree -L 3 .
.
├── app.py
├── app.spec
├── build
│   └── app
│       ├── Analysis-00.toc
│       ├── BUNDLE-00.toc
│       ├── COLLECT-00.toc
│       ├── EXE-00.toc
│       ├── PKG-00.toc
│       ├── PYZ-00.pyz
│       ├── PYZ-00.toc
│       ├── app
│       ├── app.pkg
│       ├── base_library.zip
│       ├── localpycs
│       ├── warn-app.txt
│       └── xref-app.html
├── dist
│   ├── app
│   │   ├── _internal
│   │   └── app
│   └── app.app
│       └── Contents
├── pet.icns
└── resources
    ├── rocket.png
    └── star.png

2-1. build 폴더

  • PyInstaller가 실행 파일 생성 과정에서 사용하는 중간 결과물 저장
  • 종속성 분석, 코드 분석 결과, 임시 파일, 로그 등을 포함
  • .spec 파일과 연결되어 내부 빌드 과정을 추적 가능
    • 디버깅 아니면 내용을 확인하거나 수정할 필요 없음
    • 실행 파일 관련 문제를 디버깅할 때 참고할 수 있음

2-2. dist 폴더

    • 최종 실행 파일과 그에 필요한 모든 파일이 포함된 배포용 디렉토리
    • 생성된 .exe , .app 와 같은 플랫폼에 맞는 실행 파일이 포함됨
    • PySide6 등 필요한 라이브러리와 .dll, .so, .dylib 등도 함께 복사됨
    • 이 디렉토리만 복사하면 다른 컴퓨터에서 실행 가능
    • 실제 배포 및 사용자 전달 시 사용하는 폴더

기본 icon 사용.

2-3. .spec 파일

  • PyInstaller가 executalbe file을 만들기 위해 참조하는 configuration 파일.
  • 초기 pyinstaller 명령 실행 시 자동으로 생성됨
  • 포함된 내용:
    • 어떤 스크립트를 빌드할지 (여기선 app.py)
    • 어떤 데이터 파일과 라이브러리를 포함할지
    • 실행 파일 형식 (예: 단일 파일, 창 없는 앱 등)
    • 아이콘, 경로, 히든 임포트 등 세부 설정
  • 사용자가 직접 수정하여 빌드 옵션을 세밀하게 제어할 수 있음
  • pyinstaller app.spec 명령으로 해당 설정대로 재빌드 가능
  • 프로젝트별(--name 으로 설정)로 하나씩 존재하며, 버전 관리에도 포함 가능

다음은 만들어진 .spec 파일임.

# -*- mode: python ; coding: utf-8 -*-

# PyInstaller spec 파일 - Python 애플리케이션을 실행 파일로 변환하기 위한 설정

# Analysis 클래스: 메인 Python 스크립트와 의존성을 분석
a = Analysis(
    ['app.py'],                    # 메인 Python 스크립트 파일 경로
    pathex=[],                     # 추가 모듈 검색 경로 (빈 리스트 = 기본 경로만 사용)
    binaries=[],                   # 포함할 바이너리 파일들 (DLL, SO 파일 등)
    datas=[],                      # 포함할 데이터 파일들 (이미지, 설정 파일 등)
    hiddenimports=[],              # 자동 감지되지 않는 숨겨진 import 모듈들
    hookspath=[],                  # 커스텀 hook 파일들의 경로
    hooksconfig={},                # hook 설정 딕셔너리
    runtime_hooks=[],              # 런타임에 실행될 hook 스크립트들
    excludes=[],                   # 패키징에서 제외할 모듈들
    noarchive=False,               # True시 모든 파일을 개별적으로 추출 (기본: False)
    optimize=0,                    # Python 바이트코드 최적화 레벨 (0=없음, 1=기본, 2=docstring제거)
)

# PYZ 클래스: Python 모듈들을 zip 아카이브로 압축
pyz = PYZ(a.pure)                 # Analysis에서 추출한 순수 Python 모듈들을 압축

# EXE 클래스: 실행 파일 생성 설정
exe = EXE(
    pyz,                          # 압축된 Python 모듈 아카이브
    a.scripts,                    # 스크립트 파일들
    [],                           # 추가 바이너리 (one-file 모드에서 사용)
    exclude_binaries=True,        # True: one-folder 모드, False: one-file 모드
    name='app',                   # 생성될 실행 파일의 이름
    debug=False,                  # 디버그 모드 활성화 여부
    bootloader_ignore_signals=False,  # 부트로더가 시그널을 무시할지 여부
    strip=False,                  # 바이너리에서 디버그 심볼 제거 여부 (Linux/Mac)
    upx=True,                     # UPX 압축 사용 여부 (실행 파일 크기 줄임)
    console=False,                # 콘솔 창 표시 여부 (False = GUI 애플리케이션)
    disable_windowed_traceback=False,  # 윈도우 모드에서 traceback 비활성화 여부
    argv_emulation=False,         # macOS에서 argv 에뮬레이션 사용 여부
    target_arch=None,             # 타겟 아키텍처 (None = 현재 시스템 아키텍처)
    codesign_identity=None,       # macOS 코드 서명 ID
    entitlements_file=None,       # macOS entitlements 파일 경로
)

# COLLECT 클래스: 실행 파일과 의존성들을 하나의 폴더로 수집
coll = COLLECT(
    exe,                          # 생성된 실행 파일
    a.binaries,                   # 바이너리 의존성들
    a.datas,                      # 데이터 파일들
    strip=False,                  # 바이너리 스트리핑 여부
    upx=True,                     # UPX 압축 사용 여부
    upx_exclude=[],               # UPX 압축에서 제외할 파일들
    name='app',                   # 출력 폴더 이름
)

# BUNDLE 클래스: macOS 애플리케이션 번들(.app) 생성
app = BUNDLE(
    coll,                         # COLLECT에서 수집된 파일들
    name='app.app',               # 번들 이름 (.app 확장자 포함)
    icon=None,                    # 애플리케이션 아이콘 파일 경로 (.icns 파일)
    bundle_identifier=None,       # 번들 식별자 (예: com.company.appname)
)

# 사용법:
# 1. 이 spec 파일로 빌드: pyinstaller app.spec
# 2. dist/app/ 폴더에 실행 파일이 생성됨
# 3. macOS에서는 dist/app.app 번들이 생성됨

 

좀 더 자세한 설정을 살펴본다.


3. Name 설정 (개별프로젝트 설정)

기존의 대상 main script 인 .py의 이름으로  기본 프로젝트 명이 만들어지는 대신

--name 옵션으로 넘겨주면 실행파일의 이름과 .spec파일의 이름을 지정할 수 있음.

 

builddist밑에도 해당 이름의 subdirecty가 만들어져서 따로 관리됨.

pyinstaller --name TestApp --windowed app.py

TestApp.app 가 추가됨.

 

앞서의 결과에서 이들이 추가된 것을 다음과 같이 확인 가능함.

❯ tree -L 2 .
.
├── TestApp.spec
├── app.py
├── app.spec
├── build
│   ├── TestApp
│   └── app
├── dist
│   ├── TestApp
│   ├── TestApp.app
│   ├── app
│   └── app.app
├── pet.icns
└── resources
    ├── rocket.png
    └── star.png

만들어진 TestApp.spec은 다음과 같음:

# -*- mode: python ; coding: utf-8 -*-


a = Analysis(
    ['app.py'],
    pathex=[],
    binaries=[],
    datas=[],
    hiddenimports=[],
    hookspath=[],
    hooksconfig={},
    runtime_hooks=[],
    excludes=[],
    noarchive=False,
    optimize=0,
)
pyz = PYZ(a.pure)

exe = EXE(
    pyz,
    a.scripts,
    [],
    exclude_binaries=True,
    name='TestApp',
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
    console=False,
    disable_windowed_traceback=False,
    argv_emulation=False,
    target_arch=None,
    codesign_identity=None,
    entitlements_file=None,
)
coll = COLLECT(
    exe,
    a.binaries,
    a.datas,
    strip=False,
    upx=True,
    upx_exclude=[],
    name='TestApp',
)
app = BUNDLE(
    coll,
    name='TestApp.app',
    icon=None,
    bundle_identifier=None,
)
  • exe, coll, appname에 할당되는 literal이 'TestApp'가 반영됨.

4. icon 설정  및 외부 파일 디렉토리 설정

main script와 같은 directory에 있는 .icns파일을 executable file의 아이콘으로 지정가능함.

 

다음과 같이 --icon 옵션으로 해당 파일을 지정하면 실행파일의 icon이 수정됨.

pyinstaller --icon pet.icns --name TestApp --windowed app.py

icon변경됨.

 

QPushButton등에 icon을 설정하는 경우는 resources라는 디렉토리를 지정하고 여기 밑에서 관리하는 것도 좋음.

(사실 pet.icns도 여기에 두는게 좋다)

 

이를 위해서는 다음의 명령어를 이용:

pyinstaller --icon pet.icns --add-data="resources:resources" --name TestApp --windowed app.py
  • 수행시킨 곳에 subdirectory인 resourcesdist/TestApp밑에 복사가 이루어짐.
  • Windows에선 :대신 ;을 써야 함.

참고로, --app-data는 여러번 지정하여 각각의 파일들을 지정할 수도 있음.

 

다음이 반영된 .spec파일임:

# -*- mode: python ; coding: utf-8 -*-


a = Analysis(
    ['app.py'],
    pathex=[],
    binaries=[],
    datas=[('resources', 'resources')],
    hiddenimports=[],
    hookspath=[],
    hooksconfig={},
    runtime_hooks=[],
    excludes=[],
    noarchive=False,
    optimize=0,
)
pyz = PYZ(a.pure)

exe = EXE(
    pyz,
    a.scripts,
    [],
    exclude_binaries=True,
    name='TestApp',
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
    console=False,
    disable_windowed_traceback=False,
    argv_emulation=False,
    target_arch=None,
    codesign_identity=None,
    entitlements_file=None,
    icon=['pet.icns'],
)
coll = COLLECT(
    exe,
    a.binaries,
    a.datas,
    strip=False,
    upx=True,
    upx_exclude=[],
    name='TestApp',
)
app = BUNDLE(
    coll,
    name='TestApp.app',
    icon='pet.icns',
    bundle_identifier=None,
)

5. 단일 파일로 만들기

--onefile 모드에서는 dist 폴더 안에 단일 실행 파일만 생성.

단, 외부파일 추가시 매우 파일이 커지고 실행시 이를 내부에서 압축해제하느라 로딩도 좀 더 걸리는 터라 installer 로 만드는 것을 권함.

 

Windows의 경우 InstallForge를 추천함.

https://installforge.net/

 

InstallForge | The Free Setup Creator For Windows

InstallForge | The Free Setup Creator for Windows If you are looking for an efficient and free setup creator, you have just found it. Get rid of script based or other complex and time-consuming installation creators and let InstallForge demonstrate you its

installforge.net


같이 보면 좋은 자료들

PyInstaller공식 문서: https://pyinstaller.org/en/stable/usage.html

 

PySide6를 Packaging하는 방법을 잘 설명한 문서 (강추)

https://www.pythonguis.com/tutorials/packaging-pyside6-applications-pyinstaller-macos-dmg/

 

Packaging PySide6 applications into a macOS app with PyInstaller

In this tutorial, we'll go through a series of steps to use PyInstaller to build simple and complex PySide6 applications into distributable macOS app bundles. There is not much fun in creating your own desktop applications if you can't share them with othe

www.pythonguis.com


 

728x90