Fullstack 63

heo4·2026년 7월 14일

Fullstack

목록 보기
18/70

풀스택

Python 예외 처리(Exception Handling) 정리

국비과정에서 배운 파이썬 예외 처리를 정리했다. try-except 기본 구조부터 finally/else, 커스텀 예외 클래스, 그리고 "if문 vs 예외 처리 언제 뭘 써야 하는가"까지 다룬다.

1. 예외 처리란?

프로그램 실행 중 잘못된 동작이 발생하면 예외(Exception)가 발생한다.

  • 예외 처리를 하지 않으면 프로그램이 강제 종료된다.
  • try ~ except 구문으로 예외 상황을 처리하면, 프로그램이 중단되지 않고 안전하게 동작한다.

2. try - except

기본 구조는 다음과 같다.

try:
    실행할 코드
except 예외종류:
    예외 발생 시 실행할 코드

예제 1) 0으로 나누기 예외 처리

예외 처리가 없으면 잘못된 입력이 들어오는 순간 프로그램이 그대로 죽는다.

# 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는 프로그램이 죽지 않고 그대로 실행된다.

예제 2) 여러 예외 처리

try:
    data = [10, 20, 30]
    index = int(input("인덱스 입력: "))
    print("값:", data[index])
except IndexError:
    print("인덱스 범위를 벗어났습니다.")
except ValueError:
    print("숫자를 입력해야 합니다.")

하나의 try 블록에 except를 여러 개 붙여서, 예외 종류별로 다른 처리를 할 수 있다.


3. try - finally, else

  • finally: 예외 발생 여부와 상관없이 항상 실행되는 블록
  • else: 예외가 발생하지 않았을 때만 실행되는 블록

예제 1) finally 사용

try:
    f = open("sample.txt", "w", encoding="utf-8")
    f.write("예외 처리 테스트")
    print("파일에 쓰기 완료")
except Exception as e:
    print("에러 발생:", e)
finally:
    f.close()
    print("파일 닫기 완료")

파일을 여는 작업처럼 성공하든 실패하든 반드시 정리(파일 닫기 등)가 필요한 경우 finally를 쓴다.

예제 2) else 사용

try:
    n = int(input("정수 입력: "))
except ValueError:
    print("잘못된 입력입니다.")
else:
    print("입력한 값:", n)
    print("정상적으로 실행되었습니다.")

else는 try 블록이 예외 없이 끝났을 때만 실행되는 코드를 분리해서, "정상 흐름"과 "예외 처리 흐름"을 명확히 구분하고 싶을 때 유용하다.


4. 커스텀 예외 클래스

파이썬은 Exception 클래스를 상속받아 사용자가 직접 예외 클래스를 정의할 수 있다.

예제 1) 간단한 커스텀 예외

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() vs raise 차이점 정리

항목print()raise
역할텍스트(정보)를 출력예외(오류)를 발생시킴
실행 결과콘솔에 문자열만 출력예외 발생 → 흐름이 중단되거나 except로 이동
사용 목적사용자 메시지 출력, 디버깅오류 상황을 알리고 처리 흐름을 제어
예외 발생 여부❌ 없음✅ 발생
흐름 제어프로그램이 그대로 계속 실행됨예외 발생 시 흐름이 중단되거나 분기됨
사용 예시print("안녕하세요")raise ValueError("잘못된 입력입니다.")

예제 2) 사용자 정의 예외 응용

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)

5. if문 vs 예외 처리

if문이 유리한 경우

예측 가능한 조건을 검사하고 그 자리에서 바로 처리할 수 있을 때는 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문과 예외 처리 비교

구분if문 제어예외 처리
목적조건에 따른 분기오류나 특수 상황 알림
발생 시점예측 가능예측 불가 (비정상 흐름)
처리 위치현재 함수 내호출한 쪽(상위 계층)
코드 구조단순구조적, 확장성 있음
예시로그인 실패, 입력값 누락파일 없음, 연결 실패, 비즈니스 규칙 위반

6. 연습문제

문제 1

사용자에게 두 수를 입력받아 나눗셈을 수행하기.

  • 숫자가 아닌 값을 입력하면 "숫자를 입력해야 합니다." 출력
  • 0으로 나누면 "0으로 나눌 수 없습니다." 출력
정답 코드 보기
try:
    num1 = int(input("첫 번째 정수: "))
    num2 = int(input("두 번째 정수: "))
    print(f"{num1} / {num2} = {num1 / num2}입니다.")
except ValueError:
    print("숫자만 입력하세요.")
except ZeroDivisionError:
    print("0으로는 나눌 수 없습니다.")

문제 2

커스텀 예외 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로 직접 발생시킨다.
  • 예측 가능한 정상 흐름은 if문으로, 예측 불가능한 비정상 상황은 예외 처리로 구분해서 사용하는 게 좋은 설계다.

클래스를 활용한 학생 관리 프로그램 구현하기

오늘 객체 지향 프로그래밍(OOP) 개념을 적용하여 학생 정보를 등록, 조회, 수정, 삭제(CRUD)할 수 있는 학생 관리 프로그램을 구현해 보았습니다. Student 클래스와 이를 관리하는 StudentManager 클래스로 역할을 나누어 코드를 작성했습니다.

📌 핵심 학습 내용

  • 역할 분담 (OOP): 데이터 구조(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()

🔍 코드 리뷰 & 리팩토링 포인트

1. find_student 메서드 채우기

delete_student 등에서 활용되는 검색 로직을 완성했습니다. 리스트를 순회하며 이름이 일치하는 객체를 반환하고, 없으면 None을 반환합니다.

def find_student(self, name):
    for student in self.students:
        if student.name == name:
            return student
    return None

2. modify_student 예외 처리 수정

기존 코드에서는 new_score = int(input("변경할 점수:")) 문장 때문에 사용자가 값을 입력하지 않고 엔터만 치면 ValueError가 발생했습니다. 이를 해결하기 위해 문자열로 먼저 입력을 받은 뒤 검증하도록 수정했습니다.

3. 인덱스 다루기 (enumerate)

수정 기능에서 기존 학생의 위치(인덱스)를 정확히 잡기 위해 idx += 1 방식 대신 파이썬의 내장 함수인 enumerate()를 사용하여 정확한 인덱스를 찾도록 개선했습니다.


💡 오늘 한 줄 요약

클래스를 사용하면 데이터의 형태를 정의하는 일과 데이터를 다루는 로직을 깔끔하게 분리할 수 있어 유지보수가 훨씬 쉬워진다!

0개의 댓글