파이썬 성적 관리 프로그램 만들기 — 중급 종합 실습
중급 시리즈 쭉 달려오면서 클래스도 배우고, 파일도 다뤄보고, 람다랑 컴프리헨션까지 만나봤잖아요. 바로 직전 글에선 가상환경이랑 pip로 작업 환경까지 깔끔하게 정리했고요. 근데 막상 하나씩 따로 배우다 보면 "이걸 다 합쳐서 뭘 만들 수 있지?" 싶으실 거예요. 저도 그랬거든요.
그래서 이번엔 작은 프로젝트 하나를 처음부터 끝까지 같이 만들어볼게요. 주제는 파이썬 성적 관리 프로그램입니다. 학생 이름이랑 점수를 받아서, 평균을 내고, 정렬도 하고, 파일로 저장까지 하는 콘솔 프로그램이에요.
python-16에서 만들었던 계산기 기억나시나요? 그때는 함수랑 if/while 정도로 끝났는데, 이번엔 한 단계 올려서 클래스 중심으로 갑니다. 같은 미니 프로젝트라도 결이 꽤 달라요.
우리가 만들 python 프로젝트 미리보기
먼저 완성됐을 때 어떻게 동작하는지 한번 볼게요.
--- 성적 관리 ---
1. 추가 2. 조회 3. 정렬 4. 저장 5. 불러오기 0. 종료
선택: 1
이름: 김하늘
점수들(공백 구분): 90 85 78
김하늘 학생이 추가됐어요.
선택: 3
평균이 높은 순으로 정렬했어요.
박지훈 | 점수: [72, 95, 88] | 평균: 85.0
김하늘 | 점수: [90, 85, 78] | 평균: 84.3
이거 한 방에 다 짜려고 하면 머리가 터져요. 그래서 Step별로 조금씩 쌓아갈 겁니다. 매 단계마다 "왜 이렇게 바꾸는지" 같이 짚을 거예요.
Step 1. Student 클래스부터
학생 한 명한테 묶이는 정보가 뭐가 있죠? 이름, 점수들, 그리고 평균. 이걸 따로따로 변수로 들고다니면 진짜 피곤해져요. 그래서 클래스로 묶어줍니다.
class Student:
def __init__(self, name, scores):
self.name = name # 학생 이름
self.scores = scores # 점수 리스트 예: [90, 85, 70]
def average(self):
if not self.scores: # 점수가 비어 있으면 0 반환
return 0
return sum(self.scores) / len(self.scores)
def __str__(self): # print(학생)했을 때 자동으로 호출되는 약속
avg = self.average()
return f"{self.name} | 점수: {self.scores} | 평균: {avg:.1f}"
__init__는 학생 한 명을 만들 때 이름이랑 점수를 같이 넣어주는 자리예요. python-17에서 봤던 그 친구 맞아요.
if not self.scores 이 한 줄이 사소해 보여도 중요한데요, 점수가 빈 리스트일 때 그냥 sum([]) / len([]) 하면 0으로 나누기 에러가 터지거든요. 처음에 이런 거 안 막아두면 나중에 진짜 골치 아파져요.
__str__은 좀 신기해 보이실 수 있는데, print(s) 했을 때 알아서 호출되는 메서드예요. 안 만들면 <Student object at 0x000...> 이런 외계어가 나옵니다.
테스트해볼게요.
s = Student("김하늘", [90, 85, 78]) # 학생 한 명 생성
print(s) # __str__이 호출됨
실행 결과.
김하늘 | 점수: [90, 85, 78] | 평균: 84.3
Step 2. 학생들을 담을 그릇
학생이 한 명만 있을 리는 없죠. 여러 명을 담아야 하는데, 가장 단순한 건 리스트입니다.
students = [] # Student 객체를 담는 리스트
students.append(Student("김하늘", [90, 85, 78]))
students.append(Student("박지훈", [72, 95, 88]))
딕셔너리로도 할 수 있는데, 그건 좀 더 복잡해지니까 우리는 그냥 리스트로 가요.
Step 3. 메뉴부터 만들어보기
이제 사용자가 뭘 할지 고를 수 있게 해줘야겠죠. 콘솔 프로그램의 단골 패턴, while True + input 조합입니다.
def print_menu():
print("\n--- 성적 관리 ---")
print("1. 추가 2. 조회 3. 정렬 4. 저장 5. 불러오기 0. 종료")
while True:
print_menu()
choice = input("선택: ")
if choice == "0":
print("종료합니다.")
break
else:
print(f"{choice}번을 선택하셨네요. (아직 기능 없음)")
while True 보고 "이거 영원히 도는 거 아닌가요?" 싶으셨다면 정상이에요. break를 만나면 빠져나가니까 괜찮습니다. 사용자가 0번을 누를 때까지 메뉴를 계속 보여주는 거예요.
Step 4. 학생 추가 기능 채우기
이제 1번 메뉴를 진짜 동작하게 만들어볼게요. 여기서 컴프리헨션이랑 예외 처리가 같이 등장합니다.
def add_student(students):
name = input("이름: ")
raw = input("점수들(공백 구분, 예: 90 85 70): ")
try:
scores = [int(s) for s in raw.split()] # 공백으로 쪼개고 정수로 변환
except ValueError:
print("점수는 숫자만 입력해주세요.")
return
students.append(Student(name, scores))
print(f"{name} 학생이 추가됐어요.")
raw.split()은 공백 기준으로 문자열을 잘라줘요. "90 85 70"이 ["90", "85", "70"]이 되는 거죠. 근데 아직 문자열이라서 평균을 못 내요. 그래서 컴프리헨션으로 한 번에 int로 바꿔줍니다.
만약 사용자가 "90 팔십 70" 이렇게 한글을 섞으면? ValueError가 터지죠. 그래서 try-except로 잡아서 친절하게 다시 입력하라고 알려줍니다. python-15에서 배운 그거 맞아요.
Step 5. 조회와 평균순 정렬 (sorted lambda 활용)
조회는 단순해요. 그냥 리스트 돌면서 출력.
def show_students(students):
if not students:
print("아직 등록된 학생이 없어요.")
return
for s in students:
print(s)
진짜 재밌는 건 정렬이에요. 학생들을 "평균이 높은 순"으로 정렬해야 하는데, 평균은 객체 안에 메서드로 들어있잖아요. 이럴 때 람다가 빛을 발합니다.
def sort_students(students):
students.sort(key=lambda s: s.average(), reverse=True)
print("평균이 높은 순으로 정렬했어요.")
show_students(students)
key=lambda s: s.average() 이 부분이 핵심인데요, "정렬할 때 각 학생의 평균값을 기준으로 비교해줘"라는 뜻이에요. reverse=True는 높은 순서가 위로 가게 뒤집는 거고요.
참고로 students.sort()는 원본 리스트를 직접 바꾸고, sorted(students)는 새 리스트를 만들어서 돌려줘요. 헷갈리기 쉬운 포인트라 한 번 짚고 갑니다.
Step 6. 파일로 저장하고 불러오기
여기까지 만들어도 프로그램을 끄면 다 날아가요. 슬프잖아요. 그래서 파일에 저장해둡시다.
저장 형식은 한 줄에 한 명, 이름,점수1 점수2 점수3 같은 간단한 텍스트로 갈게요.
def save_to_file(students, filename="students.txt"):
with open(filename, "w", encoding="utf-8") as f:
for s in students:
scores_str = " ".join(str(x) for x in s.scores) # 점수들을 공백으로 합치기
f.write(f"{s.name},{scores_str}\n")
print(f"{filename}에 저장했어요.")
def load_from_file(filename="students.txt"):
students = []
try:
with open(filename, "r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if not line: # 빈 줄은 건너뛰기
continue
name, scores_str = line.split(",")
scores = [int(x) for x in scores_str.split()]
students.append(Student(name, scores))
print(f"{filename}에서 불러왔어요.")
except FileNotFoundError:
print("저장된 파일이 없어요. 빈 상태로 시작합니다.")
return students
여기서 두 가지 잔소리 좀 할게요.
첫째, with open()은 꼭 써주세요. 파일을 알아서 닫아주거든요. 안 닫으면 어디선가 조용히 문제가 생깁니다.
둘째, encoding="utf-8"을 빼먹으면 한글이 깨질 수 있어요. 특히 윈도우에서요. 저도 처음에 이거 모르고 한참 헤맸어요.
객체를 파일에 어떻게 담냐고요? 객체 그대로는 못 담아요. 그래서 문자열로 풀어서 저장하고, 불러올 때 다시 객체로 조립하는 거예요. 이걸 직렬화라고 부르는데, 용어는 지금 외우실 필요 없어요.
파이썬 중급 실습 — 최종 통합 코드
자, 이제 한 자리에 다 모아봅니다.
class Student:
def __init__(self, name, scores):
self.name = name
self.scores = scores
def average(self):
if not self.scores:
return 0
return sum(self.scores) / len(self.scores)
def __str__(self):
return f"{self.name} | 점수: {self.scores} | 평균: {self.average():.1f}"
def add_student(students):
name = input("이름: ")
raw = input("점수들(공백 구분): ")
try:
scores = [int(s) for s in raw.split()]
except ValueError:
print("점수는 숫자만 입력해주세요.")
return
students.append(Student(name, scores))
print(f"{name} 학생이 추가됐어요.")
def show_students(students):
if not students:
print("아직 등록된 학생이 없어요.")
return
for s in students:
print(s)
def sort_students(students):
students.sort(key=lambda s: s.average(), reverse=True)
print("평균이 높은 순으로 정렬했어요.")
show_students(students)
def save_to_file(students, filename="students.txt"):
with open(filename, "w", encoding="utf-8") as f:
for s in students:
scores_str = " ".join(str(x) for x in s.scores)
f.write(f"{s.name},{scores_str}\n")
print(f"{filename}에 저장했어요.")
def load_from_file(filename="students.txt"):
students = []
try:
with open(filename, "r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if not line:
continue
name, scores_str = line.split(",")
scores = [int(x) for x in scores_str.split()]
students.append(Student(name, scores))
print(f"{filename}에서 불러왔어요.")
except FileNotFoundError:
print("저장된 파일이 없어요. 빈 상태로 시작합니다.")
return students
def main():
students = []
while True:
print("\n--- 성적 관리 ---")
print("1. 추가 2. 조회 3. 정렬 4. 저장 5. 불러오기 0. 종료")
choice = input("선택: ")
if choice == "0":
print("종료합니다.")
break
elif choice == "1": add_student(students)
elif choice == "2": show_students(students)
elif choice == "3": sort_students(students)
elif choice == "4": save_to_file(students)
elif choice == "5": students = load_from_file()
else: print("잘못된 입력이에요.")
if __name__ == "__main__":
main()
5번 메뉴에서 students = load_from_file() 이렇게 다시 대입하는 거 보이시죠? 파일에서 새로 불러온 리스트로 통째로 교체하는 거예요. 처음 보시면 "왜 다시 넣지?" 싶을 수 있는데, 그 이유입니다.
더 해보고 싶다면 (실습 과제)
기본형은 끝났는데, 욕심이 좀 나신다면 이런 것들을 시도해보세요.
- 학생한테 학년/반 필드를 추가해보기. 클래스 확장 연습으로 딱이에요.
- 점수를 리스트 말고 딕셔너리로 바꿔보기.
{"국어": 90, "수학": 85}이렇게요. 그러면 과목명도 같이 관리할 수 있어요. - 저장 형식을 JSON으로 바꿔보기.
import json한 줄이면 시작할 수 있는데, 이건 나중에 API 다룰 때 다시 만날 거예요.
하나라도 직접 손대보시면 진짜 많이 늘어요. 댓글로 어디서 막히셨는지 남겨주셔도 좋고요.
여기까지 따라오신 분, 진심으로 박수받으셔야 해요. 중급에서 배운 거 거의 다 한 번씩 써봤거든요. 클래스, 리스트, 함수, 파일 입출력, 예외 처리, 컴프리헨션, 람다까지요.
근데 한 가지 생각해볼 게 있어요. 지금은 학생이 100명쯤 돼도 끄떡없어요. 그럼 1000만 명이면요? 메모리에 한꺼번에 다 못 올려요. 그럴 땐 한 번에 하나씩 꺼내 쓰는 방법이 필요한데, 그게 다음 글의 주제, 이터레이터와 제너레이터예요. 중급 졸업하고 고급으로 살짝 넘어가는 길목입니다.
제 글이 도움이 되셨다면 댓글 & 공감 부탁드려요 😀
'Application > Python' 카테고리의 다른 글
| [Python] 파이썬 가상환경과 pip 완벽 정리 (Windows 기준) (0) | 2026.05.19 |
|---|---|
| [Python] 파이썬 데코레이터: 함수를 꾸며주는 마법 (@ 기호의 정체) (0) | 2026.05.15 |
| [Python] 파이썬 람다와 map filter, 한 줄 함수로 코드 짧게 쓰는 법 (0) | 2026.05.14 |
| [Python] 파이썬 리스트 컴프리헨션, 코드 한 줄로 줄이는 마법 (0) | 2026.05.13 |
| [Python] 파이썬 상속과 오버라이딩, 클래스를 또 만들지 마세요 (1) | 2026.05.12 |