Python Tkinter - 6. 실전 프로젝트와 최적화

2025. 2. 28. 14:37프로그래밍 언어/Python

6. 실전 프로젝트와 최적화

이번 주차에서는 대규모 GUI 애플리케이션 설계, 성능 최적화, 멀티스레딩, 비동기 작업 처리를 배웁니다.
특히 모듈화, 이벤트 루프 최적화, 데이터베이스 연동 및 백그라운드 작업 처리를 적용하여 고급 TODO 리스트 애플리케이션을 제작하고, 개인 프로젝트를 기획 및 구현하는 것이 목표입니다.


이론

1. 대규모 애플리케이션 설계

간단한 GUI 애플리케이션은 단일 파일로 작성해도 문제가 없지만, 기능이 많아질수록 코드의 유지보수와 성능 관리가 어려워지므로 모듈화와 클래스 기반 설계가 필요합니다.

모듈화 적용

애플리케이션을 기능별로 분리하면 코드의 가독성이 좋아지고 유지보수가 쉬워집니다.
예를 들어, 데이터베이스, UI, 이벤트 처리를 각각의 파일로 분리하면 관리가 더 용이해집니다.

📁 프로젝트 구조 예시

/todo_app
    ├── main.py          # 애플리케이션 실행 파일
    ├── database.py      # 데이터베이스 관리 모듈
    ├── ui.py            # Tkinter UI 구성 모듈
    ├── requirements.txt # 패키지 목록

database.py (데이터베이스 관련 코드)

import sqlite3

class Database:
    def __init__(self, db_name="todo.db"):
        self.conn = sqlite3.connect(db_name)
        self.cur = self.conn.cursor()
        self.cur.execute("""
            CREATE TABLE IF NOT EXISTS tasks (
                id INTEGER PRIMARY KEY, 
                task TEXT, 
                priority INTEGER, 
                deadline TEXT
            )
        """)
        self.conn.commit()

    def add_task(self, task, priority, deadline):
        self.cur.execute("INSERT INTO tasks (task, priority, deadline) VALUES (?, ?, ?)", (task, priority, deadline))
        self.conn.commit()

    def get_tasks(self):
        self.cur.execute("SELECT * FROM tasks ORDER BY priority DESC")
        return self.cur.fetchall()

    def close(self):
        self.conn.close()

이제 Database 클래스를 활용하여 데이터베이스 연산을 손쉽게 수행할 수 있습니다.


2. 성능 최적화

Tkinter GUI에서 성능을 향상시키려면 이벤트 루프 최적화, 위젯 수 감소, 메모리 관리 등의 기법을 적용해야 합니다.

1) 이벤트 루프 최적화

Tkinter는 after() 메서드를 사용하여 반복적으로 실행되는 작업을 효율적으로 처리할 수 있습니다.

def update_label():
    label.config(text="업데이트됨!")
    root.after(1000, update_label)  # 불필요한 반복 호출 방지

root = tk.Tk()
label = tk.Label(root, text="대기 중...")
label.pack()

root.after(1000, update_label)  # 1초마다 UI 업데이트
root.mainloop()

불필요한 UI 업데이트를 최소화하여 애플리케이션의 반응성을 개선합니다.


2) 위젯 수 감소 및 메모리 관리

Tkinter에서 위젯이 많아지면 성능이 저하될 수 있으므로 불필요한 위젯을 삭제하거나 숨기는 방식을 활용해야 합니다.

def toggle_label():
    """ 필요할 때만 위젯을 표시/숨김 """
    global label_visible
    if label_visible:
        label.pack_forget()  # 위젯 숨기기
    else:
        label.pack()  # 다시 표시
    label_visible = not label_visible

root = tk.Tk()
label_visible = True

label = tk.Label(root, text="이 위젯을 숨길 수 있습니다.")
label.pack()

btn = tk.Button(root, text="토글", command=toggle_label)
btn.pack()

root.mainloop()

위젯을 삭제하지 않고 pack_forget()을 사용하면 메모리를 절약할 수 있습니다.


3. 멀티스레딩 (비동기 작업 처리)

Tkinter는 싱글 스레드 기반이므로 네트워크 요청, 데이터베이스 연산 등의 작업을 실행할 때 UI가 멈출 수 있습니다.
이를 방지하려면 백그라운드 스레드에서 작업을 실행하고, UI 업데이트는 메인 스레드에서 수행해야 합니다.

📌 안전한 멀티스레딩 예제

import threading
import time
import tkinter as tk

def long_running_task():
    """ 시간이 오래 걸리는 작업을 백그라운드에서 실행 """
    time.sleep(5)
    root.after(0, lambda: label.config(text="작업 완료!"))  # UI 업데이트는 after() 사용

def start_task():
    thread = threading.Thread(target=long_running_task)
    thread.start()

root = tk.Tk()
root.title("멀티스레딩 예제")

label = tk.Label(root, text="작업 대기 중...")
label.pack()

btn = tk.Button(root, text="작업 시작", command=start_task)
btn.pack()

root.mainloop()

이제 UI가 멈추지 않고 백그라운드에서 작업을 실행할 수 있습니다.


실습: 고급 TODO 리스트 애플리케이션

📌 주요 기능

  • 카테고리, 우선순위, 마감일 관리
  • SQLite 데이터베이스 연동 및 백업 기능
  • 멀티스레딩 적용하여 UI 멈춤 방지
import threading
import sqlite3
import tkinter as tk
from tkinter import ttk

# 데이터베이스 연결
conn = sqlite3.connect("todo.db")
cur = conn.cursor()
cur.execute("CREATE TABLE IF NOT EXISTS tasks (id INTEGER PRIMARY KEY, task TEXT, priority INTEGER, deadline TEXT)")
conn.commit()

def add_task():
    """ 새로운 작업을 추가하는 함수 (멀티스레딩 적용) """
    task = entry.get()
    priority = priority_var.get()
    deadline = deadline_entry.get()
    if task:
        threading.Thread(target=lambda: add_task_db(task, priority, deadline)).start()
        
def add_task_db(task, priority, deadline):
    """ 백그라운드에서 데이터베이스 업데이트 """
    cur.execute("INSERT INTO tasks (task, priority, deadline) VALUES (?, ?, ?)", (task, priority, deadline))
    conn.commit()
    root.after(0, lambda: entry.delete(0, tk.END))
    root.after(0, update_list)

def update_list():
    """ 기존 목록을 초기화하지 않고 효율적으로 갱신 """
    cur.execute("SELECT * FROM tasks ORDER BY priority DESC")
    rows = cur.fetchall()
    tree.delete(*tree.get_children())  # 기존 항목 삭제
    for row in rows:
        tree.insert("", tk.END, values=row)

root = tk.Tk()
root.title("TODO 리스트")

entry = tk.Entry(root, width=40)
entry.grid(row=0, column=0, padx=5, pady=5)

priority_var = tk.IntVar()
priority_dropdown = ttk.Combobox(root, textvariable=priority_var, values=[1, 2, 3])
priority_dropdown.grid(row=0, column=1, padx=5, pady=5)
priority_dropdown.current(0)

deadline_entry = tk.Entry(root, width=15)
deadline_entry.grid(row=0, column=2, padx=5, pady=5)

add_btn = tk.Button(root, text="추가", command=add_task)
add_btn.grid(row=0, column=3, padx=5, pady=5)

tree = ttk.Treeview(root, columns=("ID", "Task", "Priority", "Deadline"), show="headings")
tree.heading("ID", text="ID")
tree.heading("Task", text="할 일")
tree.heading("Priority", text="우선순위")
tree.heading("Deadline", text="마감일")
tree.grid(row=1, column=0, columnspan=4, padx=5, pady=5)

update_list()
root.mainloop()

conn.close()

멀티스레딩을 활용하여 UI가 멈추지 않고 데이터를 처리할 수 있습니다.


📌 최종 정리

  • 대규모 애플리케이션 설계: 모듈화, 클래스 기반 설계 적용
  • 성능 최적화: 이벤트 루프 최적화, 메모리 관리, 위젯 수 감소
  • 멀티스레딩 적용: UI 멈춤 방지
  • 실습: SQLite 연동 TODO 리스트 제작 및 성능 최적화