Featured image of post [Python] PyInstaller를 사용한 Python 프로그램 배포 완전 가이드

[Python] PyInstaller를 사용한 Python 프로그램 배포 완전 가이드

PyInstaller를 활용하여 Python 코드를 독립 실행 가능한 프로그램으로 변환하고 배포하는 방법을 상세히 설명한다.

Python으로 개발한 애플리케이션을 다른 사용자들에게 배포할 때, Python이 설치되지 않은 환경에서도 실행할 수 있도록 해야 하는 경우가 많다. PyInstaller는 이러한 문제를 해결해주는 강력한 도구로, Python 코드를 독립 실행 가능한 executable 파일로 변환해준다.

PyInstaller란?

PyInstaller는 Python 애플리케이션을 Windows, Linux, macOS에서 실행 가능한 독립형 executable로 변환해주는 packaging tool이다. PyInstaller는 Python 프로그램과 모든 의존성을 하나의 패키지로 묶어서, 대상 시스템에 Python이 설치되지 않아도 프로그램이 실행될 수 있도록 한다.

주요 특징

  • Cross-platform 지원: Windows, Linux, macOS에서 모두 사용 가능하다
  • One-file 배포: 모든 것을 하나의 executable 파일로 패키징할 수 있다
  • 의존성 자동 탐지: 필요한 library와 module을 자동으로 찾아서 포함한다
  • 다양한 Python 버전 지원: Python 3.8~3.13을 지원한다
  • GUI 애플리케이션 지원: Tkinter, PyQt, wxPython 등을 지원한다

PyInstaller 설치

pip를 통한 설치

1
pip install pyinstaller

설치 확인

1
pyinstaller --version

정상적으로 설치되었다면 PyInstaller의 버전 정보가 출력된다.

기본 사용법

간단한 Python 파일 변환

가장 기본적인 사용법은 다음과 같다:

1
pyinstaller your_script.py

이 명령어는 다음과 같은 결과를 생성한다:

  • dist/your_script/ 디렉토리에 실행 파일과 필요한 library들이 생성된다
  • build/ 디렉토리에 중간 빌드 파일들이 생성된다
  • your_script.spec 파일이 생성된다

One-file 배포

모든 것을 하나의 executable 파일로 묶으려면 --onefile 옵션을 사용한다:

1
pyinstaller --onefile your_script.py

이 경우 dist/ 디렉토리에 단일 executable 파일이 생성된다.

고급 옵션들

주요 Command Line 옵션들

--onefile vs --onedir

1
2
3
4
5
# 하나의 파일로 패키징 (기본값은 --onedir)
pyinstaller --onefile script.py

# 디렉토리 형태로 패키징 (기본값)
pyinstaller --onedir script.py

--windowed (GUI 애플리케이션용)

1
2
# Windows에서 console 창 숨김 (GUI 애플리케이션용)
pyinstaller --windowed your_gui_app.py

Icon 설정

1
2
# Custom icon 설정
pyinstaller --onefile --icon=app.ico your_script.py

추가 데이터 파일 포함

1
2
3
4
5
# 데이터 파일 포함
pyinstaller --onefile --add-data "data.txt;." your_script.py

# Linux/macOS에서는 콜론(:) 사용
pyinstaller --onefile --add-data "data.txt:." your_script.py

Hidden imports 설정

1
2
# 자동으로 탐지되지 않는 module 강제 포함
pyinstaller --onefile --hidden-import=module_name your_script.py

고급 옵션 예제

1
2
3
4
5
6
7
8
pyinstaller \
    --onefile \
    --windowed \
    --icon=app.ico \
    --name="MyApplication" \
    --add-data "resources/*;resources/" \
    --hidden-import=pkg_resources.py2_warn \
    your_script.py

.spec 파일 활용

PyInstaller는 처음 실행할 때 .spec 파일을 생성한다. 이 파일을 수정하면 더 세밀한 제어가 가능하다.

기본 .spec 파일 구조

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# -*- mode: python ; coding: utf-8 -*-

block_cipher = None

a = Analysis(
    ['your_script.py'],
    pathex=[],
    binaries=[],
    datas=[],
    hiddenimports=[],
    hookspath=[],
    hooksconfig={},
    runtime_hooks=[],
    excludes=[],
    win_no_prefer_redirects=False,
    win_private_assemblies=False,
    cipher=block_cipher,
    noarchive=False,
)

pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)

exe = EXE(
    pyz,
    a.scripts,
    [],
    exclude_binaries=True,
    name='your_script',
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
    console=True,
    disable_windowed_traceback=False,
    argv_emulation=False,
    target_arch=None,
    codesign_identity=None,
    entitlements_file=None,
)

coll = COLLECT(
    exe,
    a.binaries,
    a.zipfiles,
    a.datas,
    strip=False,
    upx=True,
    upx_exclude=[],
    name='your_script',
)

.spec 파일 수정 예제

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# -*- mode: python ; coding: utf-8 -*-

block_cipher = None

a = Analysis(
    ['main.py'],
    pathex=['/path/to/your/project'],
    binaries=[],
    datas=[
        ('resources', 'resources'),
        ('config.ini', '.'),
    ],
    hiddenimports=[
        'pkg_resources.py2_warn',
        'requests',
    ],
    hookspath=[],
    hooksconfig={},
    runtime_hooks=[],
    excludes=[
        'tkinter',
        'matplotlib',
    ],
    win_no_prefer_redirects=False,
    win_private_assemblies=False,
    cipher=block_cipher,
    noarchive=False,
)

pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)

exe = EXE(
    pyz,
    a.scripts,
    a.binaries,
    a.zipfiles,
    a.datas,
    [],
    name='MyApp',
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
    upx_exclude=[],
    runtime_tmpdir=None,
    console=False,  # GUI 애플리케이션
    icon='app.ico',
)

.spec 파일로 빌드하기

1
pyinstaller your_script.spec

실무 사용 예제

1. Flask Web Application 패키징

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# app.py
from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello():
    return "Hello, World!"

if __name__ == '__main__':
    app.run(debug=True)
1
2
# Flask 애플리케이션 패키징
pyinstaller --onefile --add-data "templates;templates" --add-data "static;static" app.py

2. Tkinter GUI 애플리케이션 패키징

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# gui_app.py
import tkinter as tk
from tkinter import messagebox

def show_message():
    messagebox.showinfo("Information", "Hello from PyInstaller!")

root = tk.Tk()
root.title("Sample GUI App")

button = tk.Button(root, text="Click Me!", command=show_message)
button.pack(pady=20)

root.mainloop()
1
2
# GUI 애플리케이션 패키징 (console 창 숨김)
pyinstaller --onefile --windowed --icon=app.ico gui_app.py

3. 외부 라이브러리를 사용하는 애플리케이션

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# data_processor.py
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

def process_data():
    data = pd.DataFrame({
        'x': np.random.randn(100),
        'y': np.random.randn(100)
    })
    
    plt.scatter(data['x'], data['y'])
    plt.savefig('output.png')
    print("Data processed and saved to output.png")

if __name__ == '__main__':
    process_data()
1
2
# 복잡한 의존성을 가진 애플리케이션 패키징
pyinstaller --onefile --hidden-import=pandas --hidden-import=numpy --hidden-import=matplotlib data_processor.py

최적화 및 문제 해결

파일 크기 최적화

불필요한 모듈 제외

1
pyinstaller --onefile --exclude-module tkinter --exclude-module matplotlib your_script.py

UPX 압축 사용

1
2
# UPX 설치 후
pyinstaller --onefile --upx-dir=/path/to/upx your_script.py

.spec 파일에서 제외 설정

1
2
3
4
5
6
excludes=[
    'tkinter',
    'unittest',
    'test',
    'distutils',
]

일반적인 문제들과 해결방법

ModuleNotFoundError

문제: 런타임에 특정 모듈을 찾을 수 없다는 오류가 발생한다.

해결방법:

1
pyinstaller --onefile --hidden-import=missing_module your_script.py

데이터 파일 경로 문제

문제: 패키징된 애플리케이션에서 데이터 파일을 찾을 수 없다.

해결방법:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import sys
import os

def resource_path(relative_path):
    """ PyInstaller로 패키징된 애플리케이션에서 리소스 경로를 가져온다 """
    try:
        # PyInstaller에서 생성된 임시 폴더
        base_path = sys._MEIPASS
    except Exception:
        base_path = os.path.abspath(".")
    
    return os.path.join(base_path, relative_path)

# 사용 예제
data_file = resource_path('data/config.txt')

DLL 관련 오류

문제: Windows에서 특정 DLL 파일을 찾을 수 없다는 오류가 발생한다.

해결방법:

1
2
# 특정 DLL 포함
pyinstaller --onefile --add-binary "path/to/library.dll;." your_script.py

Performance 최적화

Import 최적화

1
2
3
4
5
# 필요한 모듈만 import
from module import specific_function

# 전체 모듈 import 피하기
# import module  # 피하자

Lazy Loading 활용

1
2
3
def heavy_function():
    import heavy_library  # 필요할 때만 import
    return heavy_library.process()

Virtual Environment 사용

PyInstaller 사용 시 가상환경을 사용하는 것이 좋다. 이렇게 하면 불필요한 패키지들이 포함되는 것을 방지할 수 있다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# Virtual environment 생성
python -m venv pyinstaller_env

# Virtual environment 활성화
# Windows
pyinstaller_env\Scripts\activate
# Linux/macOS
source pyinstaller_env/bin/activate

# 필요한 패키지만 설치
pip install pyinstaller
pip install -r requirements.txt

# PyInstaller 실행
pyinstaller --onefile your_script.py

배포 및 테스트

다양한 환경에서 테스트

패키징된 애플리케이션은 다음 환경에서 테스트해야 한다:

  • Python이 설치되지 않은 깔끔한 시스템
  • 다른 버전의 Windows/Linux/macOS
  • 32-bit와 64-bit 시스템

배포 패키지 준비

1
2
3
4
5
6
7
# 최종 배포용 디렉토리 구조 예제
MyApplication/
├── MyApp.exe
├── README.md
├── LICENSE
└── docs/
    └── user_manual.pdf

Installer 생성 (선택사항)

NSISInno Setup을 사용하여 Windows용 installer를 만들 수 있다:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
; NSIS 스크립트 예제
!define APPNAME "MyApplication"
!define VERSION "1.0.0"

Name "${APPNAME}"
OutFile "${APPNAME}_${VERSION}_installer.exe"
InstallDir "$PROGRAMFILES\${APPNAME}"

Section "Main"
    SetOutPath $INSTDIR
    File "dist\MyApp.exe"
    CreateShortcut "$DESKTOP\${APPNAME}.lnk" "$INSTDIR\MyApp.exe"
SectionEnd

보안 고려사항

Code Obfuscation

PyInstaller로 패키징된 실행 파일은 쉽게 역공학될 수 있다. 중요한 코드가 있다면 obfuscation을 고려해야 한다:

1
2
3
4
# PyArmor를 사용한 코드 난독화
pip install pyarmor
pyarmor obfuscate your_script.py
pyinstaller --onefile dist/your_script.py

민감한 정보 처리

1
2
3
4
5
6
# 환경변수나 외부 설정 파일 사용
import os

API_KEY = os.environ.get('API_KEY')
if not API_KEY:
    raise ValueError("API_KEY environment variable not set")

결론

PyInstaller는 Python 애플리케이션을 독립 실행 가능한 프로그램으로 배포하는 강력하고 편리한 도구이다. 기본적인 사용법부터 고급 최적화 기법까지 다양한 옵션을 제공하므로, 프로젝트의 요구사항에 맞게 적절히 활용할 수 있다.

성공적인 배포를 위해서는:

  • Virtual environment 사용으로 깔끔한 의존성 관리
  • .spec 파일 활용으로 세밀한 설정 제어
  • 다양한 환경에서의 철저한 테스트
  • 적절한 최적화를 통한 파일 크기 관리

이러한 점들을 고려하여 PyInstaller를 활용하면, Python 애플리케이션을 효과적으로 배포할 수 있다.