국비과정에서 배운 파이썬 예외 처리를 정리했다. try-except 기본 구조부터 finally/else, 커스텀 예외 클래스, 그리고 "if문 vs 예외 처리 언제 뭘 써야 하는가"까지 다룬다.
프로그램 실행 중 잘못된 동작이 발생하면 예외(Exception)가 발생한다.
try ~ except 구문으로 예외 상황을 처리하면, 프로그램이 중단되지 않고 안전하게 동작한다.기본 구조는 다음과 같다.
try:
실행할 코드
except 예외종류:
예외 발생 시 실행할 코드
예외 처리가 없으면 잘못된 입력이 들어오는 순간 프로그램이 그대로 죽는다.
# try - except 없을 때
a = int(input("나눌 숫자 a: "))
b = int(input("나눌 숫자 b: "))
result = a / b
print("결과:", result)
except로 감싸면 특정 오류 상황마다 원하는 메시지를 보여주고 프로그램을 계속 진행시킬 수 있다.
try:
a = int(input("나눌 숫자 a: "))
b = int(input("나눌 숫자 b: "))
result = a / b
print("결과:", result)
except ZeroDivisionError:
print("0으로 나눌 수 없습니다!")
except ValueError:
print("숫자를 입력해야 합니다!")
print("다음 코드...")
print("다음 코드...")
print("다음 코드...")
try 블록 안에서 어떤 예외가 발생하든, 마지막 세 줄의 print는 프로그램이 죽지 않고 그대로 실행된다.
try:
data = [10, 20, 30]
index = int(input("인덱스 입력: "))
print("값:", data[index])
except IndexError:
print("인덱스 범위를 벗어났습니다.")
except ValueError:
print("숫자를 입력해야 합니다.")
하나의 try 블록에 except를 여러 개 붙여서, 예외 종류별로 다른 처리를 할 수 있다.
finally: 예외 발생 여부와 상관없이 항상 실행되는 블록else: 예외가 발생하지 않았을 때만 실행되는 블록try:
f = open("sample.txt", "w", encoding="utf-8")
f.write("예외 처리 테스트")
print("파일에 쓰기 완료")
except Exception as e:
print("에러 발생:", e)
finally:
f.close()
print("파일 닫기 완료")
파일을 여는 작업처럼 성공하든 실패하든 반드시 정리(파일 닫기 등)가 필요한 경우 finally를 쓴다.
try:
n = int(input("정수 입력: "))
except ValueError:
print("잘못된 입력입니다.")
else:
print("입력한 값:", n)
print("정상적으로 실행되었습니다.")
else는 try 블록이 예외 없이 끝났을 때만 실행되는 코드를 분리해서, "정상 흐름"과 "예외 처리 흐름"을 명확히 구분하고 싶을 때 유용하다.
파이썬은 Exception 클래스를 상속받아 사용자가 직접 예외 클래스를 정의할 수 있다.
class NegativeNumberError(Exception):
pass
def check_positive(n):
if n < 0:
raise NegativeNumberError("음수는 허용되지 않습니다.")
return n
try:
num = int(input("양수 입력: "))
print("입력 값:", check_positive(num))
except NegativeNumberError as e:
print("에러 발생:", e)
raise로 직접 예외를 발생시키면, 그 시점부터 해당 예외를 처리할 except 블록으로 흐름이 넘어간다.
| 항목 | print() | raise |
|---|---|---|
| 역할 | 텍스트(정보)를 출력 | 예외(오류)를 발생시킴 |
| 실행 결과 | 콘솔에 문자열만 출력 | 예외 발생 → 흐름이 중단되거나 except로 이동 |
| 사용 목적 | 사용자 메시지 출력, 디버깅 | 오류 상황을 알리고 처리 흐름을 제어 |
| 예외 발생 여부 | ❌ 없음 | ✅ 발생 |
| 흐름 제어 | 프로그램이 그대로 계속 실행됨 | 예외 발생 시 흐름이 중단되거나 분기됨 |
| 사용 예시 | print("안녕하세요") | raise ValueError("잘못된 입력입니다.") |
class TooYoungError(Exception):
pass
def vote(age):
if age < 18:
raise TooYoungError("18세 미만은 투표할 수 없습니다.")
print("투표 가능합니다.")
try:
age = int(input("나이를 입력하세요: "))
vote(age)
except TooYoungError as e:
print("예외:", e)
예측 가능한 조건을 검사하고 그 자리에서 바로 처리할 수 있을 때는 if문을 쓴다. if문은 코드의 "흐름 제어"가 목적이다.
def login(username, password):
if username == "" or password == "":
print("아이디와 비밀번호를 모두 입력하세요.")
return
if username == "admin" and password == "1234":
print("로그인 성공!")
else:
print("아이디 또는 비밀번호가 잘못되었습니다.")
login("admin", "")
login("admin", "1234")
로그인 실패처럼 오류가 아니라 정상 흐름의 일부로 볼 수 있는 경우는 if문이 적합하다.
함수가 실패했을 때 그 처리를 호출한 쪽에 맡기고 싶을 때 예외 처리를 쓴다. 파일 처리, 데이터베이스 접속처럼 예측이 어려운 비정상 상황이 대표적이다.
| 구분 | if문 제어 | 예외 처리 |
|---|---|---|
| 목적 | 조건에 따른 분기 | 오류나 특수 상황 알림 |
| 발생 시점 | 예측 가능 | 예측 불가 (비정상 흐름) |
| 처리 위치 | 현재 함수 내 | 호출한 쪽(상위 계층) |
| 코드 구조 | 단순 | 구조적, 확장성 있음 |
| 예시 | 로그인 실패, 입력값 누락 | 파일 없음, 연결 실패, 비즈니스 규칙 위반 |
사용자에게 두 수를 입력받아 나눗셈을 수행하기.
"숫자를 입력해야 합니다." 출력"0으로 나눌 수 없습니다." 출력try:
num1 = int(input("첫 번째 정수: "))
num2 = int(input("두 번째 정수: "))
print(f"{num1} / {num2} = {num1 / num2}입니다.")
except ValueError:
print("숫자만 입력하세요.")
except ZeroDivisionError:
print("0으로는 나눌 수 없습니다.")
커스텀 예외 OverLimitError를 정의하고, 사용자가 입력한 점수가 0~100을 벗어나면 예외를 발생시켜 "점수는 0~100 사이여야 합니다."를 출력하기.
class OverLimitError(Exception):
pass
try:
score = int(input("점수 입력:"))
if score < 0 or score > 100:
raise OverLimitError('점수는 0 ~ 100점 사이여야 합니다.')
print("입력하신 점수는 ", score, "점입니다.", sep="")
except OverLimitError as e:
print("예외 발생:", e)
finally는 항상 실행, else는 예외가 없을 때만 실행이라는 차이를 기억하자.Exception을 상속받아 만들고, raise로 직접 발생시킨다.오늘 객체 지향 프로그래밍(OOP) 개념을 적용하여 학생 정보를 등록, 조회, 수정, 삭제(CRUD)할 수 있는 학생 관리 프로그램을 구현해 보았습니다. Student 클래스와 이를 관리하는 StudentManager 클래스로 역할을 나누어 코드를 작성했습니다.
Student)와 비즈니스 로직(StudentManager)을 클래스로 분리했습니다.__str__): 객체를 print()로 출력할 때 원하는 문자열 형태로 나오도록 오버라이딩했습니다.append(), insert(), remove()를 활용해 메모리 상에서 데이터를 관리합니다.작성한 전체 파이썬 코드입니다. (미완성된 find_student 메서드 포함)
class Student:
"""학생 한 명의 정보를 담는 클래스"""
def __init__(self, name, age, score):
self.name = name
self.age = age
self.score = score
def __str__(self):
return f"이름:{self.name} / 나이:{self.age}살 / 점수:{self.score}점"
class StudentManager:
"""학생 목록을 관리하는 클래스"""
MAX_COUNT = 4 # 최대 등록 제한
def __init__(self):
self.students = []
def find_student(self, name):
"""이름으로 학생을 찾아서 반환 (없으면 None)"""
# [과제] 코드 작성 구역
for student in self.students:
if student.name == name:
return student
return None
def add_student(self):
if len(self.students) > self.MAX_COUNT - 1:
print("더 이상 등록할 수 없습니다.")
return
name = input("이름 입력:")
age = int(input("나이 입력:"))
score = int(input("점수 입력:"))
student = Student(name, age, score)
self.students.append(student)
def list_students(self):
if not self.students:
print("등록된 학생이 없습니다.")
return
print()
print("=== 학생 목록 ===")
for student in self.students:
print(student)
def search_student(self):
if not self.students:
print("등록된 학생이 없습니다.")
return
search_name = input("검색할 학생 이름:")
flag = False
search_student = None
for student in self.students:
if search_name == student.name:
flag = True
search_student = student
if not flag:
print("해당 학생은 존재하지 않습니다.")
return
else:
print(search_student)
def modify_student(self):
if not self.students:
print("등록된 학생이 없습니다.")
return
modify_name = input("수정할 학생 이름:")
flag = False
idx = 0
modify_student = None
# 순회하며 인덱스와 객체 찾기
for i, student in enumerate(self.students):
if modify_name == student.name:
idx = i
flag = True
modify_student = student
break
if not flag:
print("해당 학생은 존재하지 않습니다.")
return
else:
new_name = input("변경할 이름:")
if new_name == "":
new_name = modify_student.name
new_age = input("변경할 나이:")
if new_age == "":
new_age = modify_student.age
else:
new_age = int(new_age)
# 버그 방지를 위해 엔터 입력 시 기존 점수 유지 처리
new_score_input = input("변경할 점수:")
if new_score_input == "":
new_score = modify_student.score
else:
new_score = int(new_score_input)
# 기존 위치의 학생을 삭제하고 새 객체로 교체하거나 직접 수정 가능
self.students[idx] = Student(new_name, new_age, new_score)
print("학생 정보가 수정되었습니다.")
def delete_student(self):
if not self.students:
print("등록된 학생이 없습니다.")
return
delete_name = input("삭제할 학생 이름:")
student = self.find_student(delete_name)
if student:
self.students.remove(student)
print("학생을 삭제하였습니다.")
else:
print("존재하지 않는 학생입니다.")
def print_menu(self):
print("\n1. 학생 등록 | 2. 학생 목록 | 3. 학생 검색 |
4. 학생 수정 | 5. 학생 삭제 | 0. 프로그램 종료")
def run(self):
while True:
self.print_menu()
cmd = int(input("명령어 입력:"))
if cmd == 1:
self.add_student()
elif cmd == 2:
self.list_students()
elif cmd == 3:
self.search_student()
elif cmd == 4:
self.modify_student()
elif cmd == 5:
self.delete_student()
elif cmd == 0:
print("프로그램을 종료합니다.")
break
if __name__ == "__main__":
manager = StudentManager()
manager.run()
find_student 메서드 채우기delete_student 등에서 활용되는 검색 로직을 완성했습니다. 리스트를 순회하며 이름이 일치하는 객체를 반환하고, 없으면 None을 반환합니다.
def find_student(self, name):
for student in self.students:
if student.name == name:
return student
return None
modify_student 예외 처리 수정기존 코드에서는 new_score = int(input("변경할 점수:")) 문장 때문에 사용자가 값을 입력하지 않고 엔터만 치면 ValueError가 발생했습니다. 이를 해결하기 위해 문자열로 먼저 입력을 받은 뒤 검증하도록 수정했습니다.
enumerate)수정 기능에서 기존 학생의 위치(인덱스)를 정확히 잡기 위해 idx += 1 방식 대신 파이썬의 내장 함수인 enumerate()를 사용하여 정확한 인덱스를 찾도록 개선했습니다.
클래스를 사용하면 데이터의 형태를 정의하는 일과 데이터를 다루는 로직을 깔끔하게 분리할 수 있어 유지보수가 훨씬 쉬워진다!