๐Ÿงฎ Python๊ณผ Tkinter๋ฅผ ์‚ฌ์šฉํ•œ ํ˜„๋Œ€ ํ†ต๊ณ„ ๊ณ„์‚ฐ๊ธฐ ๋งŒ๋“ค๊ธฐ

๋ฐœํ–‰: (2025๋…„ 12์›” 28์ผ ์˜คํ›„ 09:50 GMT+9)
6 min read
์›๋ฌธ: Dev.to

Source: Dev.to

์œ„ ๋งํฌ์— ํฌํ•จ๋œ ํ…์ŠคํŠธ๋ฅผ ๋ฒˆ์—ญํ•˜๋ ค๋ฉด ํ•ด๋‹น ๋‚ด์šฉ์„ ์ œ๊ณตํ•ด ์ฃผ์‹œ๊ฒ ์–ด์š”?
๋งํฌ ์ž์ฒด๋Š” ๊ทธ๋Œ€๋กœ ๋‘๊ณ , ๋ฒˆ์—ญ์ด ํ•„์š”ํ•œ ๋ณธ๋ฌธ์„ ์•Œ๋ ค์ฃผ์‹œ๋ฉด ํ•œ๊ตญ์–ด๋กœ ๋ฒˆ์—ญํ•ด ๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค.

๊ฐœ์š”

Python์€ ๋ฐ์ดํ„ฐ ๊ณผํ•™ ์ƒํƒœ๊ณ„๋กœ ์ž์ฃผ ์นญ์ฐฌ๋ฐ›์ง€๋งŒ, ์œ ์šฉํ•œ ๊ฒƒ์„ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด ๋ฐ˜๋“œ์‹œ Pandas๋‚˜ NumPy๊ฐ€ ํ•„์š”ํ•˜์ง€๋Š” ์•Š์Šต๋‹ˆ๋‹ค.

์ด๋ฒˆ ํฌ์ŠคํŠธ์—์„œ๋Š” StatMate๋ฅผ ๋งŒ๋“ค ๊ฒƒ์ž…๋‹ˆ๋‹ค โ€“ ๊น”๋”ํ•˜๊ณ  ํ˜„๋Œ€์ ์ธ ๋ฐ์Šคํฌํ†ฑ ํ†ต๊ณ„ ๊ณ„์‚ฐ๊ธฐ์ด๋ฉฐ, ๋‹ค์Œ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค:

  • ๐ŸชŸ Tkinter โ€“ Python์˜ ํ‘œ์ค€ GUI ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
  • ๐ŸŽจ svโ€‘ttk โ€“ Tkinter์šฉ ํ˜„๋Œ€์ ์ธ ํ…Œ๋งˆ
  • ๐Ÿ“Š statistics โ€“ Python ๋‚ด์žฅ ํ†ต๊ณ„ ๋ชจ๋“ˆ

๋งˆ์ง€๋ง‰๊นŒ์ง€ ์ง„ํ–‰ํ•˜๋ฉด ๋‹ค์Œ์„ ๊ณ„์‚ฐํ•˜๋Š” ์ •๊ตํ•œ GUI ์•ฑ์„ ์–ป๊ฒŒ ๋ฉ๋‹ˆ๋‹ค:

  • ํ‰๊ท 
  • ์ค‘์•™๊ฐ’
  • ์ตœ๋นˆ๊ฐ’
  • ๋ถ„์‚ฐ
  • ํ‘œ์ค€ ํŽธ์ฐจ
  • ์ตœ์†Œ / ์ตœ๋Œ€

โ€ฆ์‹ค์‹œ๊ฐ„ ์ž…๋ ฅ ๊ฒ€์ฆ ๋ฐ ์กฐ์ • ๊ฐ€๋Šฅํ•œ ์†Œ์ˆ˜์  ์ž๋ฆฌ์ˆ˜ ๊ธฐ๋Šฅ์„ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.

โœจ ๊ธฐ๋Šฅ

  • svโ€‘ttk๋ฅผ ์‚ฌ์šฉํ•œ ํ˜„๋Œ€์ ์ธ ๋ผ์ดํŠธ ํ…Œ๋งˆ
  • ์‹ค์‹œ๊ฐ„ ์ž…๋ ฅ ๊ฒ€์ฆ
  • ํ”Œ๋ ˆ์ด์Šคํ™€๋” ํ…์ŠคํŠธ UX
  • ์กฐ์ • ๊ฐ€๋Šฅํ•œ ์†Œ์ˆ˜์  ์ •๋ฐ€๋„
  • ๊น”๋”ํ•œ ์ƒํƒœ ํ‘œ์‹œ์ค„ ํ”ผ๋“œ๋ฐฑ
  • ์šฐ์•„ํ•œ ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ

๐Ÿ“ฆ ์š”๊ตฌ ์‚ฌํ•ญ

Only one external dependency is needed:

pip install sv-ttk

Everything else comes from Pythonโ€™s standard library.

๐Ÿง  ๋””์ž์ธ ๊ฐœ์š”

์•ฑ์€ ๋…ผ๋ฆฌ์ ์ธ ์„น์…˜์œผ๋กœ ๊ตฌ์„ฑ๋ฉ๋‹ˆ๋‹ค:

  1. Helpers โ€“ ์ƒํƒœ ์—…๋ฐ์ดํŠธ, ํฌ๋งทํŒ…, ๊ณ„์‚ฐ ๋“œ๋ผ์ด๋ฒ„
  2. Validation โ€“ ์ •๊ทœ์‹ ๊ธฐ๋ฐ˜ ์ˆซ์ž ์ž…๋ ฅ ํ•„ํ„ฐ๋ง
  3. Statistics Logic โ€“ statistics ๋ชจ๋“ˆ ์‚ฌ์šฉ
  4. UI Layout โ€“ ์นด๋“œ, ๋ผ๋ฒจ, ๋ฒ„ํŠผ ๋ฐ ๊ฒฐ๊ณผ ํŒจ๋„
  5. UX Enhancements โ€“ ํ”Œ๋ ˆ์ด์Šคํ™€๋” ํ…์ŠคํŠธ, ์ƒํƒœ ๋ฐ”, ์ •๋ฐ€๋„ ์ œ์–ด

๐Ÿงฎ ๊ณ„์‚ฐ์ด ์ž‘๋™ํ•˜๋Š” ๋ฐฉ์‹

์‚ฌ์šฉ์ž๋Š” ์ˆซ์ž๋ฅผ ์‰ผํ‘œ๋กœ ๊ตฌ๋ถ„๋œ ๋ชฉ๋ก์œผ๋กœ ์ž…๋ ฅํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ:

1, 2, 3, 4, 5

์•ฑ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค:

  1. ์ž…๋ ฅ์„ ํŒŒ์‹ฑํ•˜๊ณ  ์ •์ œํ•ฉ๋‹ˆ๋‹ค
  2. ๊ฐ’์„ float ๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค
  3. ํ†ต๊ณ„๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค
  4. ์„ ํƒ๋œ ์ •๋ฐ€๋„์— ๋”ฐ๋ผ ์ถœ๋ ฅ์„ ํฌ๋งทํ•ฉ๋‹ˆ๋‹ค

ํŠน์ˆ˜ ๊ฒฝ์šฐ(์˜ˆ: ๊ณ ์œ  ๋ชจ๋“œ๊ฐ€ ์—†๊ฑฐ๋‚˜ ๊ฐ’์ด ํ•˜๋‚˜๋ฟ์ธ ๊ฒฝ์šฐ)๋„ ์ •์ƒ์ ์œผ๋กœ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค.

๐Ÿง‘โ€๐Ÿ’ป ์ „์ฒด ์†Œ์Šค ์ฝ”๋“œ

import tkinter as tk
from tkinter import ttk, messagebox
import sv_ttk
import statistics
import re

# =========================
# Helpers
# =========================
def set_status(msg):
    status_var.set(msg)
    root.update_idletasks()

def fmt(value):
    try:
        p = precision_var.get()
        return f"{value:.{p}f}"
    except Exception:
        return str(value)

def calculate_stats():
    raw_input = numbers_entry.get()
    if raw_input.strip() == 'e.g., 1, 2, 3, 4':
        messagebox.showerror("Invalid Input", "Please enter a valid list of numbers.")
        return
    try:
        numbers = [float(x.strip()) for x in raw_input.split(",") if x.strip() != ""]
        if not numbers:
            raise ValueError

        mean_val = statistics.mean(numbers)
        median_val = statistics.median(numbers)
        try:
            mode_val = statistics.mode(numbers)
        except statistics.StatisticsError:
            mode_val = "No unique mode"
        variance_val = statistics.variance(numbers) if len(numbers) > 1 else 0
        stdev_val = statistics.stdev(numbers) if len(numbers) > 1 else 0

        result_var.set(
            f"๐Ÿ“Š Mean: {fmt(mean_val)}\n"
            f"๐Ÿ“Œ Median: {fmt(median_val)}\n"
            f"๐ŸŽฏ Mode: {mode_val}\n"
            f"๐Ÿ“ˆ Variance: {fmt(variance_val)}\n"
            f"๐Ÿ“‰ Std. Dev.: {fmt(stdev_val)}\n"
            f"๐Ÿ”ฝ Min: {fmt(min(numbers))}\n"
            f"๐Ÿ”ผ Max: {fmt(max(numbers))}"
        )
        set_status("โœ… Statistics calculated successfully")
    except ValueError:
        messagebox.showerror("Invalid Input", "Please enter a valid list of numbers.")

def validate_input(new_value):
    if new_value == '' or new_value == 'e.g., 1, 2, 3, 4':
        return True
    return re.match(r'^[0-9.,\s-]*$', new_value) is not None

# =========================
# App Setup
# =========================
root = tk.Tk()
root.title("StatMate - Full Statistics Tool")
root.geometry("950x650")
root.minsize(950, 650)
sv_ttk.set_theme("light")

precision_var = tk.IntVar(value=4)
status_var = tk.StringVar(value="Ready")

ttk.Label(root, textvariable=status_var, anchor="w",
          font=("Segoe UI", 10), padding=(10, 5)
).pack(side="bottom", fill="x")

main_frame = ttk.Frame(root, padding=20)
main_frame.pack(expand=True, fill="both")

ttk.Label(main_frame, text="StatMate",
          font=("Segoe UI", 28, "bold")
).pack(anchor="w")

ttk.Label(main_frame, text="Full Statistics Calculator",
          font=("Segoe UI", 14), foreground="#555"
).pack(anchor="w", pady=(0, 20))

input_card = ttk.LabelFrame(main_frame, text="Input Numbers & Precision", padding=20)
input_card.pack(fill="x", pady=(0, 20))

input_row = ttk.Frame(input_card)
input_row.pack(fill="x")

def on_entry_click(event):
    if numbers_entry.get() == 'e.g., 1, 2, 3, 4':
        numbers_entry.delete(0, "end")
        numbers_entry.config(fg='black')

def on_focusout(event):
    if numbers_entry.get() == '':
        numbers_entry.insert(0, 'e.g., 1, 2, 3, 4')
        numbers_entry.config(fg='grey')

ttk.Label(input_row, text="Numbers:").grid(row=0, column=0)
numbers_entry = tk.Entry(input_row, font=("Segoe UI", 14), width=40, fg='grey')
numbers_entry.insert(0, 'e.g., 1, 2, 3, 4')
numbers_entry.bind('', on_entry_click)
numbers_entry.bind('', on_focusout)
numbers_entry.grid(row=0, column=1, padx=(5, 20))

vcmd = (root.register(validate_input), '%P')
numbers_entry.config(validate='key', validatecommand=vcmd)

ttk.Label(input_row, text="Decimal Precision:").grid(row=0, column=2)
ttk.Spinbox(input_row, from_=0, to=10,
            textvariable=precision_var, width=5
).grid(row=0, column=3, padx=(5, 0))

ttk.Button(main_frame, text="๐Ÿ“Š Calculate Statistics",
           command=calculate_stats,
           style="Accent.TButton"
).pack(pady=20, ipadx=10, ipady=10)

result_card = ttk.LabelFrame(main_frame, text="Results", padding=20)
result_card.pack(fill="both", expand=True)

result_var = tk.StringVar(value="โ€”")
tk.Label(result_card, textvariable=result_var,
         font=("Segoe UI", 16, "bold"),
         justify="left", anchor="nw",
         bd=2, relief="groove", padx=10, pady=10
).pack(fill="both", expand=True)

root.mainloop()

๐Ÿš€ Possible Improvements

  • CSV๋กœ ๊ฒฐ๊ณผ ๋‚ด๋ณด๋‚ด๊ธฐ
  • ํžˆ์Šคํ† ๊ทธ๋žจ ์‹œ๊ฐํ™” ์ถ”๊ฐ€
  • ๋‹คํฌโ€‘๋ชจ๋“œ ํ† ๊ธ€
  • ํ‚ค๋ณด๋“œ ๋‹จ์ถ•ํ‚ค
  • ๋ฐ์ดํ„ฐ์…‹ ์ €์žฅ / ๋ถˆ๋Ÿฌ์˜ค๊ธฐ

๐ŸŽฏ Final Thoughts

Tkinter๊ฐ€ ๊ตฌ์‹์ผ ํ•„์š”๋Š” ์—†์Šต๋‹ˆ๋‹ค. ์•ฝ๊ฐ„์˜ ๊ตฌ์กฐ, ๊ฒ€์ฆ, ๊ทธ๋ฆฌ๊ณ  ํ˜„๋Œ€์ ์ธ ํ…Œ๋งˆ๋ฅผ ์ ์šฉํ•˜๋ฉด Python์ด ์‹คํ–‰๋˜๋Š” ์–ด๋””์„œ๋“  ์ž‘๋™ํ•˜๋Š” ์„ธ๋ จ๋˜๊ณ  ๊ธฐ๋Šฅ์ ์ธ ํ†ต๊ณ„ ๋„๊ตฌ๋ฅผ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฆ๊ฑฐ์šด ์ฝ”๋”ฉ ๋˜์„ธ์š”!

์ด ํ”„๋กœ์ ํŠธ๊ฐ€ ๋งˆ์Œ์— ๋“ค์—ˆ๋‹ค๋ฉด ํ™•์žฅํ•˜๊ฑฐ๋‚˜ ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ํ†ต๊ณ„ ํˆดํ‚ท์œผ๋กœ ์ „ํ™˜ํ•ด ๋ณด์„ธ์š”.

์ฆ๊ฑฐ์šด ๊ตฌ์ถ• ๋˜์„ธ์š”! ๐Ÿง ๐Ÿ“Š

StatMate launches with a clean, modern interface built using Tkinter and svโ€‘ttk. The layout is designed for clarity and ease of use.

Back to Blog

๊ด€๋ จ ๊ธ€

๋” ๋ณด๊ธฐ ยป

Tkinter๋ฅผ ์‚ฌ์šฉํ•œ 2D ๊ฒŒ์ž„ ์‹œ์ž‘ํ•˜๊ธฐ (ํŒŒํŠธ 9): ์นด์šดํ„ฐ ํ‘œ์‹œ

์นด์šดํ„ฐ ํ‘œ์‹œ ์ด ๊ธฐ์‚ฌ์—์„œ๋Š” ํ™”๋ฉด์— ๋‚จ์•„ ์žˆ๋Š” ์•…๋งˆ ์ˆ˜๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ์นด์šดํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ๋จผ์ €, ์นด์šดํ„ฐ๋ผ๋Š” ๋ณ€์ˆ˜๋ฅผ ๋งŒ๋“ค์–ด ๋‚จ์€ ์ˆ˜๋ฅผ ์ถ”์ ํ•ฉ๋‹ˆ๋‹ค.

Ruby GUI๋Š” ์‚ฌ๋ผ์กŒ๋‚˜์š”โ€ฆ ์•„๋‹ˆ๋ฉด ์‚ด์•„ ์žˆ๋‚˜์š”?

๋ฐฐ๊ฒฝ: ๋‚˜๋Š” Ruby๋กœ ๊ฐ„๋‹จํ•œ ๋ฐ์Šคํฌํ†ฑ ์•ฑ์„ ๋งŒ๋“ค๊ณ  ์‹ถ์—ˆ๋‹คโ€”GitHub ํ†ต๊ณ„ fetcher๋กœ, ์‚ฌ์šฉ์ž ์ด๋ฆ„์„ ์ž…๋ ฅํ•˜๋ฉด ํ•ด๋‹น ์‚ฌ์šฉ์ž์˜ avatar, followers, ๊ทธ๋ฆฌ๊ณ  repositories๋ฅผ ํ‘œ์‹œํ•œ๋‹ค....