💡 AI 인사이트

🤖 AI가 여기에 결과를 출력합니다...

댓글 커뮤니티

쿠팡이벤트

이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

검색

    [코담] 웹개발·실전 프로젝트·AI까지, 파이썬·장고의 모든것을 담아낸 강의와 개발 노트

    예외처리 | ✅저자: 이유정(박사)

    🔹 예외처리의 기본 개념

    ◽ 예외(Exception)란? 프로그램을 실행하는 도중에 정상적인 흐름을 방해하는 문제 상황을 의미합니다.예를 들어, 사용자가 숫자가 아닌 문자를 입력했거나, 존재하지 않는 파일을 열려고 할 때처럼, 개발자가 예상할 수 있는 상황에서 발생하는 문제가 바로 예외입니다.

    오류(Error)는 이와는 조금 다른 개념: 주로 시스템적인 문제나 컴퓨터 내부 자원의 한계 때문에 발생하며, 예를 들면 메모리 부족, 하드웨어 고장 같은 문제들이 여기에 해당합니다. 이런 오류는 보통 개발자가 직접 해결하거나 처리하기 어렵고, 프로그램이 갑자기 멈추는 원인이 됩니다.


    ◽ 예외 발생 구조

    • 스택 트레이스란?
      오류가 어떤 파일의 몇 번째 줄에서, 어떤 함수 호출 중에 발생했는지를 보여주는 메시지입니다.

    예외가 발생하면 Python은 다음과 같은 순서로 동작:

    1. 코드를 실행하다가 문제(예외) 발생
    2. Python은 예외 객체를 생성하고, 예외를 상위 코드로 전달
    3. 예외를 처리할 try-except 블록이 있으면 → 그곳에서 처리
    4. 예외를 처리할 코드가 없으면 → 프로그램 강제 종료
    5. 종료 시 스택 트레이스(Stack Trace) 출력

    ◽ 내장 예외 클래스란? 내장 예외 클래스란 파이썬에서 자동으로 제공하는 오류 객체의 종류입니다. `프로그램 실행 중 문제가 발생했을 때, 파이썬은 해당 상황에 맞는 특정 예외 클래스를 자동으로 발생시킵니다.

    즉, 우리가 별도로 정의하지 않아도 파이썬이 미리 만들어둔 "오류의 
    이름표"들이며,  
    각 예외 클래스는 어떤 종류의 문제인지 알려주고,
    개발자가 try–except로 잡아서 처리할 수 있게 도와줍니다.
    
    ✅ 내장 예외 클래스 정리표
    예외 이름 설명 발생 예시 코드
    ValueError 잘못된 값이 들어왔을 때 발생 int("abc"), float("xyz"), 나이 음수
    TypeError 잘못된 타입 연산 또는 호출 None + 1, list("abc")()
    IndexError 리스트/튜플 등에서 유효하지 않은 인덱스 접근 시 발생 arr[10]
    KeyError 딕셔너리에서 없는 키로 접근 시 발생 user["email"]
    ZeroDivisionError 0으로 나누기 시도 10 / 0
    FileNotFoundError 없는 파일을 열려고 할 때 발생 open("nofile.txt")
    PermissionError 읽기/쓰기 권한이 없는 파일/디렉터리에 접근 시 발생 open("/sys/test.txt", "w")
    OSError 파일/디렉터리 등 OS 관련 에러 전체 (상위 예외) 경로 오류, 디스크 문제 등
    AttributeError 객체에 존재하지 않는 속성에 접근할 때 발생 None.strip()
    Exception 모든 예외의 최상위 부모 클래스 except Exception as e:
    IsADirectoryError 파일 대신 디렉토리를 열려고 할 때 발생 open("logs/", "r")
    TimeoutError 네트워크 요청이 시간 초과될 때 발생 (requests) requests.get(..., timeout=3)
    ConnectionError 네트워크 연결에 실패했을 때 발생 (requests) requests.get("http://invalid.url")
    UnboundLocalError 참조 전에 지역 변수 사용 시 발생 file.close() 호출 전 file이 정의 안됨
    RuntimeError 실행 중 알 수 없는 문제 발생 시 수동으로 발생시킴 raise RuntimeError("오류 발생")

    ✅ 예외 카테고리 분류
    카테고리 관련 예외 이름들
    값/형식 관련 ValueError, TypeError, AttributeError
    컬렉션/자료구조 IndexError, KeyError
    수학 연산 관련 ZeroDivisionError, ArithmeticError
    파일/디렉토리 I/O FileNotFoundError, PermissionError, IsADirectoryError, OSError, UnboundLocalError
    네트워크 관련 ConnectionError, TimeoutError
    기타/범용 처리 Exception, RuntimeError

    </> 예시코드: 예외 발생과 처리 없이 종료

    def divide(a, b):
        return a / b
    
    print("나눗셈 시작")
    result = divide(10, 0)
    print("결과:", result)
    

    🖨️ 출력결과:

    나눗셈 시작
    Traceback (most recent call last):
      File "example.py", line 5, in <module>
        result = divide(10, 0)
      File "example.py", line 2, in divide
        return a / b
    ZeroDivisionError: division by zero
    

    🔍 해설:

    • 10 / 0은 수학적으로 불가능하므로 ZeroDivisionError 예외가 발생합니다.
    • 이 예외를 처리하지 않았기 때문에 프로그램이 중단되고, 스택 트레이스가 출력됩니다.

    </> 예시코드: 예외 발생과 처리 없이 종료

    def divide(a, b):
        try:
            result = a / b
            print("결과:", result)
        except ZeroDivisionError as e:
            print("예외 발생:", e)
    
    print("나눗셈 시작")
    divide(10, 0)
    print("프로그램 종료")
    

    🖨️ 출력결과:

    나눗셈 시작
    ⚠️ 예외 발생: division by zero
    프로그램 종료
    

    🔍 해설:

    • try 블록에서 예외가 발생하면, except 블록이 실행됩니다.
    • 예외를 print로 출력하고, 프로그램은 정상적으로 종료됩니다.
    • 이렇게 예외를 잡아서 처리(handling) 하면, 프로그램이 멈추지 않고 계속 진행할 수 있습니다.

    ◽ try-except 구문 try-except 구문은 예외(Exception)가 발생할 수 있는 코드를 안전하게 실행하기 위해 사용하는 예외 처리 도구입니다. 프로그래밍을 하다 보면 예상치 못한 문제가 발생할 수 있습니다. 예를 들어, 0으로 나누거나, 없는 파일을 열려고 하거나, 사용자가 잘못된 입력을 했을 경우 등이 그렇습니다. 이런 상황이 생기면 프로그램이 갑자기 멈추게 되는데, 이걸 막기 위해 try-except 구문을 사용합니다.

    📖 문법, 구문(syntax): 기본적인 구조

    try:
        # 문제가 생길 수 있는 코드
    except 예외종류:
        # 문제가 생겼을 때 실행할 코드
    

    </> 예시코드:

    def get_user_age():
        try:
            age = int(input("나이를 숫자로 입력하세요: "))
            if age < 0:
                raise ValueError("나이는 음수가 될 수 없습니다.")
            print(f"입력한 나이는 {age}세입니다.")
        except ValueError as e:
            print(f"입력 오류 발생: {e}")
    
    get_user_age()
    

    🖨️ 출력결과:

    나이를 숫자로 입력하세요: 20
    입력한 나이는 20세입니다.
    
    나이를 숫자로 입력하세요: 스무살
    입력 오류 발생: invalid literal for int() with base 10: '스무살'
    

    🔍 해설:

    • int()로 변환할 수 없는 문자를 입력하면 ValueError 발생
    • 음수를 입력한 경우엔 개발자가 직접 예외를 발생시켜 (raise) 처리
    • 사용자 경험을 해치지 않도록 에러 메시지를 명확히 출력함
    • 현업에서는 사용자 입력 유효성 검사에 자주 사용됨

    </> 예시코드:

    def read_config_file(path):
        try:
            with open(path, "r", encoding="utf-8") as f:
                config = f.read()
                print("설정 파일 내용:")
                print(config)
        except FileNotFoundError:
            print(f"설정 파일을 찾을 수 없습니다: {path}")
        except Exception as e:
            print(f"알 수 없는 오류 발생: {e}")
    
    read_config_file("sample.txt")
    

    🖨️ 출력결과:

    설정 파일을 찾을 수 없습니다: config/settings.txt
    

    🔍 해설:

    • 설정 파일을 열 때, 파일이 없으면 FileNotFoundError가 발생
    • 예상치 못한 모든 예외를 Exception으로 처리 (현업에서 안정성 확보용)
    • 예외 상황에서도 프로그램이 멈추지 않도록 설계됨
    • 서버 설정, 로깅, 초기화 파일 등 현업에서 매우 자주 사용되는 패턴

    ✨ 실무 팁

    • 예외 처리를 너무 광범위하게 하지 말고, 예상 가능한 예외만 세부적으로 처리하는 것이 좋습니다.
    • except Exception은 정말 예외 상황을 몰라서 긴급하게 잡고 싶을 때만 사용하세요.
    • 사용자가 문제를 이해할 수 있도록 에러 메시지를 구체적이고 친절하게 보여주는 것이 중요합니다.

    ◽ else / finally : try-except와 함께 쓰는 보조절 예외 블록

    else란? else는 예외가 발생하지 않았을 때만 실행되는 블록입니다. 즉, try 안의 코드가 성공적으로 실행되었을 때만 else 블록이 실행됩니다. - 예외가 발생하면 except가 실행되고 else는 건너뜁니다. - 예외가 없으면 except는 건너뛰고 else가 실행됩니다.

    주로 try 안에서 예외가 나면 실행하면 안 되는 후속 작업을 else에 
    둡니다. 예를 들어, 정상 계산이 끝난 뒤 저장하거나 출력하는 작업을 
    넣는 경우가 많습니다.
    

    finally란? finally는 예외 발생 여부와 상관없이 무조건 실행되는 블록입니다.
    즉, 예외가 발생해도, 발생하지 않아도 항상 마지막에 실행됩니다. - 파일을 열었으면 닫고, - 데이터베이스 연결을 했으면 끊고, - 네트워크 연결을 종료하고, - 로그를 남기는 등의 정리(clean-up) 작업에 주로 사용됩니다.

    • 예외 처리 구조 전체 흐름
    try:
        # 예외 발생 가능 코드
    except 예외타입:
        # 예외 발생 시 실행
    else:
        # 예외가 없을 때 실행
    finally:
        # 예외 발생 여부와 관계없이 항상 실행
    
    • try: 예외가 발생할 수 있는 의심 코드

    • except: 예외가 발생했을 때 처리하는 코드

    • else: 예외가 발생하지 않았을 때만 실행되는 코드

    • finally: 예외 발생과 상관없이 항상 실행되는 정리 코드

    • "문제가 생길 수도 있는 코드"를 먼저 시도하고 (try),

    • 문제가 생기면 처리하고 (except),

    • 아무 문제 없이 끝나면 추가 작업도 하고 (else),

    • 문제가 있든 없든 마지막에 반드시 정리까지 하자 (finally)


    </> 예시코드: elsefinally를 활용한 파일 처리

    def read_config_file(path):
        try:
            file = open(path, "r", encoding="utf-8")
            config = file.read()
        except FileNotFoundError:
            print(f"파일이 존재하지 않습니다: {path}")
        else:
            print("설정 파일 내용:")
            print(config)
        finally:
            try:
                file.close()
                print("파일 닫기 완료")
            except Exception:
                print("파일 객체가 정의되지 않아 닫을 수 없음")
    
    read_config_file("sample.txt")
    
    def read_config_file(path):
        try:
            # 1. 파일 열기 시도 (실패할 수도 있음)
            file = open(path, "r", encoding="utf-8")
            config = file.read()
        except FileNotFoundError:
            # 2. 파일이 없으면 이쪽으로 넘어옴
            print(f"파일이 존재하지 않습니다: {path}")
        else:
            # 3. 에러 없이 잘 열렸다면 파일 내용 출력
            print("설정 파일 내용:")
            print(config)
        finally:
            # 4. 에러가 나든 안 나든 무조건 파일 닫기 시도
            try:
                file.close()
                print("파일 닫기 완료")
            except Exception:
                print("파일 객체가 정의되지 않아 닫을 수 없음")
    
    

    ✔️ 의사코드:

    함수 read_config_file(파일경로):
        시도:
            파일을 읽기 모드로 열고 → file 변수에 저장
            파일 안의 내용을 읽어서 → config 변수에 저장
    
        만약 파일이 없다는 오류가 발생하면:
            "파일이 존재하지 않습니다"라는 메시지 출력
    
        그렇지 않고 파일을 잘 읽었다면:
            "설정 파일 내용:" 출력
            읽은 내용(config)을 출력
    
        마지막으로 무조건 실행:
            시도:
                file 객체를 닫는다
                "파일 닫기 완료" 메시지 출력
            만약 file이 정의되지 않아 닫을 수 없다면:
                "파일 객체가 정의되지 않아 닫을 수 없음" 메시지 출력
    

    🔍 해설:

    • 파일 열기 시도
    • 파일이 없으면 오류 메시지 출력
    • 파일을 정상적으로 읽으면 내용 출력
    • 무조건 file.close() 시도하여 정리 (닫기)
    • file 변수가 없으면 예외 메시지 출력

    🖨️ 출력결과:

    # 파일이 존재할 경우
    설정 파일 내용:
    (파일 내용 출력)
    파일 닫기 완료
    
    # 파일이 없을 경우
    파일이 존재하지 않습니다: config/settings.txt
    파일 객체가 정의되지 않아 닫을 수 없음
    

    </> 예시코드: elsefinally 활용한 숫자 계산 (예외 없는 경우 후속 처리)

    def divide(a, b):
        try:
            result = a / b
        except ZeroDivisionError:
            print("0으로는 나눌 수 없습니다.")
        else:
            print(f"나눗셈 결과: {result}")
        finally:
            print("연산 시도 완료")
    
    divide(10, 2)  # 정상적인 경우
    divide(10, 0)  # 예외 발생
    

    ✔️ 의사코드:

    함수 divide(숫자1, 숫자2):
    
        시도:
            숫자1을 숫자2로 나눈다 → result에 저장
    
        만약 숫자20이라서 나눌 수 없으면:
            "0으로는 나눌 수 없습니다." 출력
    
        그렇지 않고 나눗셈이 성공하면:
            "나눗셈 결과: [결과값]" 출력
    
        마지막으로 무조건 실행:
            "연산 시도 완료" 출력
    

    🔍 해설:

    • divide(10, 2) → 정상적인 나눗셈 → 결과 출력 → "연산 시도 완료" 출력
    • divide(10, 0)ZeroDivisionError 발생 → 예외 메시지 출력 → "연산 시도 완료" 출력

    🖨️ 출력결과:

    # 정상 실행 시
    나눗셈 결과: 5.0
    연산 시도 완료
    
    # 예외 발생 시
    0으로는 나눌 수 없습니다.
    연산 시도 완료
    

    ✨ 실무 팁:

    • else: 예외가 없을 때만 후속 작업 실행 (예: 파일 읽은 후 처리, DB 트랜잭션 완료 등)

    • finally: 파일, DB, 네트워크 등 항상 정리해야 하는 자원을 안전하게 종료하는 데 사용


    ◽ 예외 메시지 Python에서 try-except 구문으로 예외를 처리할 때 except SomeError as e: 형식을 사용하면, 발생한 예외 객체를 e라는 이름으로 받아 그 안에 들어 있는 오류 메시지를 추출하거나 로깅할 수 있습니다.

    이렇게 하면 예외의 종류와 내용을 개발자 또는 사용자에게 구체적으로 
    알려줄 수 있는 장점이 있습니다.
    
    주로 로그 저장, 사용자 친화적 메시지 출력, 디버깅에 사용됩니다.
    

    </> 예시코드: 사용자 입력 예외 메시지 추출

    def get_age():
        try:
            age = int(input("나이를 입력하세요: "))
            if age < 0:
                raise ValueError("나이는 음수가 될 수 없습니다.")
            print(f"입력한 나이: {age}세")
        except ValueError as e:
            print(f" 입력 오류: {e}")
    
    get_age()
    

    🔍 코드해설:

    • 숫자가 아닌 문자를 입력하거나 음수를 입력하면 ValueError가 발생합니다.
    • as e를 통해 발생한 예외 메시지를 e에 담고 출력합니다.

    ✔️ 의사코드:

    함수 get_age():
        시도:
            사용자에게 나이 입력받기 → 정수로 변환
            음수이면 예외 발생
            나이 출력
        숫자 변환 오류 또는 음수이면:
            예외 메시지를 출력
    

    🖨️ 출력결과:

    # 입력오류(음수입력시):
    입력 오류: 나이는 음수가 될 수 없습니다.
    
    # 입력오류(문자입력시):
    invalid literal for int() with base 10: 'abc'
    

    </> 예시코드: 파일 열기 실패 메시지 로깅

    import datetime
    
    def read_log(path):
        try:
            with open(path, "r", encoding="utf-8") as f:
                print(f.read())
        except FileNotFoundError as e:
            error_time = datetime.datetime.now().isoformat()
            print(f"[{error_time}] 파일 오류: {e}")
    
    read_log("logs/missing_file.txt")
    

    🔍 코드해설:

    • 파일이 존재하지 않을 경우 FileNotFoundError가 발생합니다.
    • as e를 사용해 예외 객체에서 메시지를 추출하고, 에러 발생 시간과 함께 출력합니다.
    • 실무에서 에러 로그 기록 등에 자주 쓰이는 패턴입니다.

    ✔️ 의사코드:

    함수 read_log(파일경로):
        시도:
            파일을 열고 내용 출력
        파일이 없으면:
            현재 시간과 예외 메시지를 함께 출력
    

    🖨️ 출력결과:

    [2025-05-21T15:45:12.345678] 파일 오류: [Errno 2] No such file or directory: 'logs/missing_file.txt'
    

    📝 문제1] 사용자로부터 나이를 입력받고 정수로 변환하여 출력하세요.
    만약 정수가 아닌 값을 입력하면 "숫자로 입력해주세요"를 출력하세요.
    입력이 성공하면 "입력 완료" 메시지를 출력하세요.
    (단, else 절을 활용하세요.)

    🖨️ 출력결과: 입력: "스물셋"

    숫자로 입력해주세요
    

    🖨️ 출력결과: 입력: "23"

    입력 완료
    당신의 나이: 23
    

    ✅ 정답:

    try:
        age = int(input("나이를 입력하세요: "))
    except ValueError:
        print("숫자로 입력해주세요")
    else:
        print("입력 완료")
        print(f"당신의 나이: {age}")
    

    🔍 해설:

    • int() 변환에서 ValueError 발생 가능
    • except에서 오류 메시지를 출력하고,
    • 에러 없이 실행되면 else 블록에서 성공 메시지를 출력

    📝 문제2] 다음 코드에서 try, except, finally를 이용해
    0으로 나누기를 방지하고, 항상 "프로그램 종료"를 출력하도록 수정하세요.

    🖨️ 출력결과:

    0으로 나눌 수 없습니다.
    프로그램 종료
    

    ✅ 정답:

    try:
        result = 10 / 0
    except ZeroDivisionError:
        print("❌ 0으로 나눌 수 없습니다.")
    finally:
        print("프로그램 종료")
    

    🔍 해설:

    • try 블록에서 예외 발생 (10 / 0)
    • except 블록에서 예외 처리
    • finally는 예외 발생 여부와 관계없이 항상 실행됨

    📝 문제3] rocess_input(value) 함수는 정수로 변환할 수 없는 값을 받으면 ValueError가 발생합니다.
    이 함수는 예외 메시지를 as e로 받아 출력하고, 오류가 없으면 결과를 출력합니다. 아래의 기능을 만족하도록 함수를 완성하세요:

    🖨️ 출력결과: 입력값이 "hello"일 경우

    예외 메시지: invalid literal for int() with base 10: 'hello'
    

    🖨️ 출력결과: 입력값이 "7"일 경우

    입력값 * 2: 14
    

    ✅ 정답:

    def process_input(value):
        try:
            num = int(value)
            print(f"입력값 * 2: {num * 2}")
        except ValueError as e:
            print(f"예외 메시지: {e}")
    
    process_input("hello")
    process_input("7")
    

    🔍 해설:

    • int()에서 오류 발생 가능 → except ValueError as e로 메시지 추출
    • as e를 통해 예외 메시지를 문자열로 받아 출력 가능
    • 정상적인 값일 경우 num * 2의 결과를 출력

    🔹 예외의 종류 및 대표적인 예외 상황

    ◽ 내장 예외(Built-in Exceptions) Python에서 기본적으로 제공하는 예외들로, 대표적으로 다음과 같은 것들이 있습니다: - ZeroDivisionError: 0으로 나눴을 때 - ValueError: 형식이 잘못된 값을 처리하려 할 때 - TypeError: 잘못된 타입의 값을 연산하거나 함수 인자로 줄 때

    </> 예시코드: ValueError와 사용자 입력

    def get_temperature():
        try:
            temp = float(input("현재 온도를 숫자로 입력하세요: "))
            print(f"입력된 온도: {temp}°C")
        except ValueError:
            print("숫자가 아닌 값을 입력했습니다. 예: 25, -3.5 등")
    
    get_temperature()
    

    🖨️ 출력결과: (예: "abc" 입력 시)

    숫자가 아닌 값을 입력했습니다. 예: 25, -3.5

    🔍 해설:

    • float()는 문자열을 실수로 변환할 때 사용되며, "abc"처럼 숫자가 아닌 값을 입력하면 ValueError가 발생합니다.
    • 이 오류를 except로 잡아 사용자가 올바른 형식을 입력하도록 유도합니다.
    • 실무에서 사용자 입력 유효성 검사 시 매우 자주 사용됩니다.

    </> 예시코드: TypeError와 함수 인자 타입 오류

    def calculate_discount(price, rate):
        try:
            discounted = price * (1 - rate)
            print(f"할인된 가격: {discounted}원")
        except TypeError as e:
            print(f"타입 오류 발생: {e}")
    
    # 잘못된 타입 예시: rate에 문자열 전달
    calculate_discount(10000, "10%")
    

    🖨️ 출력결과:

    타입 오류 발생: unsupported operand type(s) for -: 'int' and 'str'
    

    🔍 해설:

    • rate 매개변수는 0.1과 같은 실수가 들어와야 하지만, "10%"처럼 문자열을 넣으면 1 - rate 연산 시 TypeError가 발생합니다.
    • 실무에서는 입력값이 외부 API나 사용자 입력으로부터 들어올 때 타입 체크가 매우 중요합니다.
    • as e를 사용하여 구체적인 오류 메시지를 출력하고 디버깅에 활용합니다.

    ◽ 사용자 입력 오류 사용자 입력 오류란, 사용자가 프로그램을 사용할 때
    예상하지 못한 잘못된 값을 입력했을 경우 발생하는 문제를 말합니다.

    예를 들어,
    - 숫자를 입력해야 하는데 문자를 입력하거나
    - 선택지에 없는 값을 입력하거나
    - 공백이나 특수문자를 잘못 넣었을 때
        
    이러한 오류는 프로그램이 멈추거나 예상과 다른 결과를 초래할 수 
    있기 때문에, 미리 예외 처리를 통해 막는 것이 중요합니다.
    

    </> 예시코드: 숫자 입력 오류 (ValueError)

    def get_user_age():
        try:
            age = int(input("당신의 나이를 숫자로 입력하세요: "))
            print(f"당신은 {age}세입니다.")
        except ValueError:
            print("❌ 숫자가 아닌 값을 입력하셨습니다. 예: 25, 30")
    
    get_user_age()
    

    🖨️ 출력결과: (사용자가 "스물다섯" 입력 시)

    숫자가 아닌 값을 입력하셨습니다. 예: 25, 30
    

    🔍 해설:

    • int()는 문자열을 정수로 바꾸는 함수입니다.
    • 사용자가 숫자가 아닌 글자("스물다섯")를 입력하면 ValueError가 발생합니다.
    • 이를 예외 처리하여 사용자에게 다시 올바른 입력을 유도합니다.
    • 실무에서는 폼 입력값 검증, 설정값 입력, 콘솔 기반 앱 등에서 자주 쓰입니다.

    </> 예시코드: 문자열 선택지 오류 (if + 예외)

    def choose_color():
        try:
            color = input("좋아하는 색을 선택하세요 (red, green, blue): ").lower()
            if color not in ["red", "green", "blue"]:
                raise ValueError("제공된 선택지에 없는 색입니다.")
            print(f"당신이 선택한 색은 {color}입니다.")
        except ValueError as e:
            print(f"입력 오류: {e}")
    
    choose_color()
    

    🖨️ 출력결과: (사용자가 "yellow" 입력 시)

    입력 오류: 제공된 선택지에 없는 색입니다.
    

    🔍 해설:

    • 사용자의 입력값을 ["red", "green", "blue"] 리스트로 제한합니다.
    • 그 외의 입력값은 예외로 처리하여 사용자에게 안내 메시지를 보여줍니다.
    • raise ValueError()는 개발자가 직접 예외를 발생시켜 오류 처리를 유도하는 실무 패턴입니다.
    • 실무에서 메뉴 선택, 옵션 제한, 콘솔 앱 선택지 처리에 매우 자주 사용됩니다.

    ◽ 파일 입출력 오류 파일 입출력 오류란, 프로그램이 파일을 읽거나 쓰는 도중 발생하는 문제입니다.

    대표적인 예외는 다음과 같습니다:
    - FileNotFoundError: 존재하지 않는 파일을 열려고 할 때 발생합니다.
    - PermissionError: 읽기 또는 쓰기 권한이 없는 파일/폴더에 접근하려
    고 할 때 발생합니다.
        
    실무에서는 로그 파일, 설정 파일, CSV, 텍스트 파일 등을 다룰 때 
    자주 발생하는 오류입니다.
    

    </> 예시코드: FileNotFoundError – 존재하지 않는 파일 열기

    def load_user_data(filepath):
        try:
            with open(filepath, "r", encoding="utf-8") as f:
                print(f.read())
        except FileNotFoundError as e:
            print(f"파일을 찾을 수 없습니다: {e}")
    
    load_user_data("data/user_info.txt")
    

    🖨️ 출력결과:

    파일을 찾을 수 없습니다: [Errno 2] No such file or directory: 'data/user_info.txt'
    

    🔍 해설:

    • 존재하지 않는 경로 data/user_info.txt를 열려고 했기 때문에 FileNotFoundError 발생
    • 실무에서 자주 발생하는 문제이며, 사용자에게 경고를 주거나 기본 파일을 생성하는 처리를 추가하기도 함

    </> 예시코드: PermissionError – 쓰기 권한 없는 경로에 파일 저장 시도

    def save_log_to_protected_path():
        try:
            with open("/system/log.txt", "w", encoding="utf-8") as f:
                f.write("시스템 로그 기록\n")
        except PermissionError as e:
            print(f"권한 오류: {e}")
    
    save_log_to_protected_path()
    

    🖨️ 출력결과:

    권한 오류: [Errno 13] Permission denied: '/system/log.txt'
    

    🔍 해설:

    • /system/ 경로는 일반 사용자에게 쓰기 권한이 없는 시스템 보호 폴더
    • PermissionError는 관리자 권한 없이 접근 불가능한 디렉토리나 파일을 다루려 할 때 발생
    • 실무에서는 로그 저장, 설정 백업, 리포트 내보내기 등에서 발생하며, 권한 안내 또는 경로 수정 유도가 필요

    ◽ 추가 파일 입출력 예외 및 처리 방식 IsADirectoryError 파일을 열어야 하는 곳에 디렉토리 경로를 잘못 입력했을 때 발생합니다.
    예: open("logs/") → logs는 파일이 아니라 디렉토리라서 열 수 없음

    </> 예시코드:

    def read_file_wrong_path():
        try:
            with open("logs/", "r", encoding="utf-8") as f:
                print(f.read())
        except IsADirectoryError as e:
            print(f"디렉토리를 파일처럼 열려고 했습니다: {e}")
    
    read_file_wrong_path()
    

    🖨️ 출력결과:

    디렉토리를 파일처럼 열려고 했습니다: [Errno 21] Is a directory: 'logs/'
    

    🔍 해설:

    • 파일이 아닌 폴더 경로를 열면 발생
    • 실무에서는 파일과 디렉토리를 구분하지 않은 잘못된 경로 지정 시 자주 발생
    • 해결 방법: os.path.isfile()로 먼저 확인하거나, 파일 확장자 확인을 추가

    📝 문제1] 리스트에서 존재하지 않는 인덱스를 참조할 경우 발생하는 예외를 처리하세요. 0~2번 인덱스만 있는 리스트에서 4번 인덱스를 출력해보세요.

    🖨️ 출력결과:

    존재하지 않는 인덱스입니다.
    

    ✅ 정답코드:

    data = [10, 20, 30]
    
    try:
        print(data[4])
    except IndexError:
        print("❌ 존재하지 않는 인덱스입니다.")
    

    🔍 코드해설: IndexError는 리스트, 튜플, 문자열 등에서 범위를 벗어난 인덱스를 접근할 때 발생합니다.


    📝 문제2] 딕셔너리에서 존재하지 않는 키로 접근할 경우 발생하는 예외를 처리하세요.

    🖨️ 출력결과:

    찾을 수 없는 키입니다.
    

    ✅ 정답코드:

    user = {"name": "홍길동"}
    
    try:
        print(user["email"])
    except KeyError:
        print("찾을 수 없는 키입니다.")
    

    🔍 코드해설: KeyError는 딕셔너리에서 존재하지 않는 키를 사용했을 때 발생합니다.


    📝 문제3] 사용자에게 숫자를 입력받고 정수로 변환하세요. 숫자가 아닌 값을 입력하면 오류 메시지를 출력하세요.

    🖨️ 출력결과:

    숫자를 입력해주세요.
    

    ✅ 정답코드:

    try:
        num = int(input("숫자를 입력하세요: "))
    except ValueError:
        print("숫자를 입력해주세요.")
    

    🔍 코드해설: int() 변환 중 숫자가 아닌 문자열이 들어오면 ValueError가 발생합니다.


    📝 문제4] 사용자가 None을 입력하거나 리스트를 입력할 경우, float() 변환 시 예외가 발생합니다. 이를 처리하는 코드를 작성하세요.

    🖨️ 출력결과:

    숫자로 변환할 수 없는 타입입니다.
    

    ✅ 정답코드:

    def convert_to_float(data):
        try:
            return float(data)
        except (ValueError, TypeError):
            print("숫자로 변환할 수 없는 타입입니다.")
    
    convert_to_float(None)
    

    🔍 코드해설: float()는 문자열이나 숫자만 허용하고, None, list, dictTypeError를 발생시킵니다.


    📝 문제5] 존재하지 않는 파일을 읽으려고 할 때 발생하는 예외를 처리하세요.

    🖨️ 출력결과:

    파일을 찾을 수 없습니다.
    

    ✅ 정답코드:

    try:
        with open("notfound.txt", "r") as f:
            print(f.read())
    except FileNotFoundError:
        print("파일을 찾을 수 없습니다.")
    

    🔍 코드해설: open() 함수는 파일이 없을 경우 FileNotFoundError를 발생시킵니다.


    📝 문제6] 쓰기 권한이 없는 경로에 파일을 저장하려고 할 때 발생하는 예외를 처리하세요.

    🖨️ 출력결과:

    쓰기 권한이 없습니다.
    

    ✅ 정답코드:

    try:
        with open("/system/output.txt", "w") as f:
            f.write("테스트")
    except PermissionError:
        print("쓰기 권한이 없습니다.")
    

    🔍 코드해설: 시스템 보호 디렉토리 등 권한이 없는 경로에 쓰기를 시도하면 PermissionError가 발생합니다.


    📝 문제7] 파일 대신 디렉토리 경로를 열려고 할 때 발생하는 예외를 처리하세요.

    🖨️ 출력결과:

    디렉토리는 파일처럼 열 수 없습니다.
    

    ✅ 정답코드:

    try:
        with open("logs/", "r") as f:
            print(f.read())
    except IsADirectoryError:
        print("디렉토리는 파일처럼 열 수 없습니다.")
    

    🔍 코드해설: IsADirectoryError는 파일을 기대한 곳에 디렉토리가 있을 때 발생하는 예외입니다.


    📝 문제8] 경로가 잘못되었거나 디스크 문제가 있는 경우를 포괄적으로 처리하세요 (OSError 사용).

    🖨️ 출력결과:

    파일 열기 중 시스템 오류 발생
    

    ✅ 정답코드:

    try:
        with open("/invalid/path/data.txt", "r") as f:
            print(f.read())
    except OSError:
        print("파일 열기 중 시스템 오류 발생")
    

    🔍 코드해설: OSErrorFileNotFoundError, PermissionError, IsADirectoryError를 포함하는 상위 예외 클래스입니다.


    IOError (또는 OSError) 입출력 과정에서 발생하는 광범위한 오류의 상위 클래스
    Python 3에서는 대부분의 입출력 오류가 OSError 또는 그 하위 클래스로 나뉨 예: 디스크 문제, 경로 오류, 장치 오류 등

    </> 예시코드:

    def write_to_file(path):
        try:
            with open(path, "w", encoding="utf-8") as f:
                f.write("테스트 데이터 저장")
        except OSError as e:
            print(f"입출력 오류 발생: {e}")
    
    write_to_file("/invalid_path/test.txt")
    

    🖨️ 출력결과:

    입출력 오류 발생: [Errno 2] No such file or directory: '/invalid_path/test.txt'
    

    🔍 해설:

    • 존재하지 않는 디렉토리 경로에 파일을 쓰려고 해서 OSError 발생
    • IOError는 과거 Python 2에서 사용되었으며, 현재는 OSError로 통합됨
    • 실무에서는 파일 경로 유효성 검사를 미리 수행하거나, 디렉토리 자동 생성 로직을 포함

    open() 실패 대응 방식 – 실무 처리 전략

    실무에서 open() 실패를 방지하고 안정성 있게 처리하기 위한 방법들: try-except 기본적인 예외 처리. 실패 시 사용자에게 오류 메시지 제공

    os.path.exists(path) 파일 또는 폴더 존재 여부 확인

    os.path.isfile(path) / os.path.isdir(path) 경로가 파일인지, 폴더인지 구분 가능

    os.makedirs(path, exist_ok=True) 파일 쓰기 전 필요한 폴더가 없으면 생성

    with open(...) open() 후 자동으로 닫히게 함 (finally 대체용)

    </> 예시코드: 안전한 파일 저장

    import os
    
    def safe_write(path, content):
        folder = os.path.dirname(path)
        try:
            if not os.path.exists(folder):
                os.makedirs(folder)
            with open(path, "w", encoding="utf-8") as f:
                f.write(content)
            print("파일 저장 완료")
        except Exception as e:
            print(f"파일 저장 실패: {e}")
    
    safe_write("logs/output.txt", "예외 없이 파일 저장하기")
    
    

    🖨️ 출력결과:

    파일 저장 완료
    

    🔍 해설:

    • 경로에 필요한 폴더가 없으면 os.makedirs()로 생성
    • 모든 예외는 Exception으로 받아 처리
    • open() 실패 가능성을 최소화하고 실무 환경에서 안정적인 동작 보장

    ◽ 네트워크 및 API 오류 네트워크 및 API 오류는 인터넷이나 외부 서버와의 통신 중 발생하는 예외입니다.
    실무에서 외부 API를 호출하거나 웹 요청을 보낼 때 자주 마주하게 됩니다.

    TimeoutError

    • 서버로 요청을 보냈지만 응답이 너무 오래 걸려서 시간 초과된 경우 발생
      (예: 네트워크 불안정, 서버 지연 등)

    ConnectionError 서버에 연결 자체가 실패했을 때 발생 (예: 인터넷 끊김, 잘못된 도메인, 서버 다운)

    이러한 예외는 웹 스크래핑, API 호출, 외부 서비스 연동 등 대부분의 네트워크 기반 프로그램에서 매우 중요하게 다루는 요소입니다.

    </> 예시코드: ConnectionError - 외부 API 연결 실패 처리

    import requests
    
    def fetch_weather():
        try:
            response = requests.get("https://invalid-api.weather.com/data")
            print(response.json())
        except requests.exceptions.ConnectionError as e:
            print(f"연결 실패: {e}")
    
    fetch_weather()
    

    🖨️ 출력결과:

    연결 실패: HTTPSConnectionPool(host='invalid-api.weather.com', port=443): Max retries exceeded ...
    

    🔍 해설:

    • 존재하지 않는 도메인이나 인터넷 연결 문제가 있을 경우 ConnectionError 발생
    • requests 라이브러리를 사용할 때 매우 자주 발생하며, URL 설정 실수나 서버 문제를 진단할 수 있음

    </> 예시코드: TimeoutError - 응답 지연 시 시간 초과 처리

    import requests
    
    def fetch_slow_api():
        try:
            response = requests.get("https://httpbin.org/delay/10", timeout=3)
            print(response.text)
        except requests.exceptions.Timeout as e:
            print(f"요청 시간 초과: {e}")
    
    fetch_slow_api()
    

    🖨️ 출력결과:

    요청 시간 초과: HTTPSConnectionPool(host='httpbin.org', port=443): Read timed out. (read timeout=3)
    

    🔍 해설:

    • httpbin.org/delay/10은 10초 뒤에 응답을 주는 테스트용 지연 API입니다.
    • timeout=3은 3초 안에 응답을 못 받으면 Timeout 예외를 발생시킵니다.
    • 실무에서는 대기 시간을 제한하여 사용자 경험을 보호하거나 재시도 로직과 함께 사용함

    ✨ 실무 팁

    • requests.get() 사용 시 timeout=초를 항상 명시하는 것이 권장됩니다.
    • try-except로 네트워크 오류를 잡아야 서비스 중단 없이 사용자에게 안내 가능
    • 필요 시 Retry 또는 백오프 전략(backoff)과 함께 자동 재시도 구현 가능

    ◽ 데이터 처리 오류 데이터 처리 오류란, 리스트, 딕셔너리, 타입 변환 등 데이터 조작 중 발생하는 오류를 말합니다.

    대표적인 예외는 다음과 같습니다:

    • IndexError: 존재하지 않는 인덱스 번호로 리스트나 튜플에 접근할 때 발생
    • KeyError: 딕셔너리에 존재하지 않는 키를 조회할 때 발생
    • ValueError: 형식이 맞지 않는 데이터를 숫자나 날짜 등으로 변환하려 할 때 발생

    이런 예외들은 실무에서 CSV 파일 파싱, 폼 처리, API 응답 처리, 데이터 분석 코드에서 매우 자주 등장합니다.

    </> 예시코드: IndexError+ValueError – 리스트 인덱스 초과 및 형변환 오류

    def parse_scores(data):
        try:
            score_str = data[3]  # 인덱스 초과 가능
            score = int(score_str)  # 문자 → 정수로 변환
            print(f"입력된 점수: {score}")
        except IndexError:
            print("데이터에 충분한 항목이 없습니다.")
        except ValueError:
            print("점수는 숫자로 입력되어야 합니다.")
    
    # 정상 리스트: parse_scores(["80", "90", "85", "70"])
    # 인덱스 초과 + 문자열 오류
    parse_scores(["80", "90", "eight"])
    

    🖨️ 출력결과:

    데이터에 충분한 항목이 없습니다.
    

    🔍 해설:

    • data[3]은 4번째 항목을 가져오는데, 리스트에 3개만 있으면 IndexError 발생
    • 값이 "eight"처럼 숫자로 변환 불가능하면 ValueError 발생
    • 실무에서는 데이터 수집 또는 사용자 입력 배열 처리 시 반드시 필요한 예외 처리

    </> 예시코드: KeyError – 딕셔너리에 없는 키 접근

    def print_user_info(user):
        try:
            print(f"이름: {user['name']}")
            print(f"이메일: {user['email']}")
        except KeyError as e:
            print(f"누락된 정보: {e} 키가 존재하지 않습니다.")
    
    user_data = {
        "name": "홍길동",
        # "email" 키가 빠진 상태
    }
    
    print_user_info(user_data)
    

    🖨️ 출력결과:

    이름: 홍길동
    누락된 정보: 'email' 키가 존재하지 않습니다.
    

    🔍 해설:

    • 딕셔너리에서 없는 키(email)를 조회하면 KeyError 발생
    • 실무에서는 API 응답이나 폼 데이터에서 특정 필드가 없을 수도 있기 때문에, get()이나 예외처리로 안정성 확보 필요
    • user.get("email", "정보 없음")처럼 대체값을 사용하는 것도 일반적인 실무 패턴

    ◽ 클래스/객체 관련 오류 클래스나 객체를 다룰 때 자주 발생하는 오류들입니다.
    아래는 대표적인 예외입니다:

    • AttributeError:
      존재하지 않는 속성(변수나 메서드 등)에 접근하려고 할 때 발생합니다.
    • TypeError:
      함수처럼 호출할 수 없는 객체를 호출하려고 했을 때 발생합니다.
      예: 숫자나 리스트를 함수처럼 쓰거나, 메서드 대신 속성에 괄호를 붙였을 때 등

    이러한 예외는 실무에서 객체 지향 프로그래밍, 클래스 기반 API, 모델 속성 접근 등에서 매우 자주 발생합니다.

    </> 예시코드: AttributeError – 존재하지 않는 속성에 접근

    class User:
        def __init__(self, name):
            self.name = name
    
    def print_user_info(user):
        try:
            print(f"이름: {user.name}")
            print(f"이메일: {user.email}")  # 존재하지 않는 속성
        except AttributeError as e:
            print(f"속성 오류: {e}")
    
    user1 = User("홍길동")
    print_user_info(user1)
    

    🖨️ 출력결과:

    이름: 홍길동
    속성 오류: 'User' object has no attribute 'email'
    

    🔍 해설:

    • User 클래스에는 name만 있고 email 속성은 없습니다.
    • 실무에서는 API 응답 데이터 객체, ORM 모델 객체 등을 다룰 때 속성 존재 여부 체크 없이 접근해서 AttributeError가 자주 발생합니다.
    • 예방 방법: hasattr() 또는 try-except 활용

    </> 예시코드: TypeError – 함수가 아닌 객체를 호출 시도

    class Config:
        def __init__(self):
            self.api_key = "abc123"
    
    config = Config()
    
    try:
        config()  # 함수가 아닌 객체를 호출
    except TypeError as e:
        print(f"호출 오류: {e}")
    

    🖨️ 출력결과:

    호출 오류: 'Config' object is not callable
    

    🔍 해설:

    • config()는 클래스 인스턴스를 함수처럼 호출하려고 해서 TypeError 발생
    • 실무에서 변수 이름과 함수 이름을 혼동하거나, 속성 이름과 메서드 이름이 헷갈릴 때 흔히 생김
    • 예방 방법: 괄호 사용 여부 확인, __call__ 메서드를 구현하지 않았다면 호출 불가

    📝 문제1] IOError (또는 OSError) 존재하지 않는 경로에서 파일을 열려고 할 때 발생할 수 있는 시스템 오류를 OSError로 처리하세요.

    🖨️ 출력결과:

    파일 시스템 오류 발생
    

    ✅ 정답:

    try:
        with open("/invalid/path/data.txt", "r") as f:
            print(f.read())
    except OSError:
        print("파일 시스템 오류 발생")
    

    🔍 해설: OSErrorIOError, FileNotFoundError 등 여러 파일 관련 예외의 상위 클래스입니다.


    📝 문제1] 파일이 잠겨있거나 디스크 오류가 발생한 경우에도 처리 가능한 예외는 무엇이며, 이를 활용한 예외처리를 작성하세요.

    🖨️ 출력결과:

    파일을 열 수 없습니다. 시스템 관련 예외 발생
    

    ✅ 정답:

    try:
        with open("/locked/device.txt", "r") as f:
            print(f.read())
    except OSError:
        print("파일을 열 수 없습니다. 시스템 관련 예외 발생")
    

    🔍 해설: OSError는 권한 문제, 잠금 문제, 디스크 오류 등 시스템 전반의 예외를 포괄합니다.


    📝 문제1] 파일 열기 전에 존재 여부를 확인하여 오류를 사전에 방지하는 방법을 작성하세요.

    🖨️ 출력결과:

    파일이 존재하지 않아 열 수 없습니다.
    

    ✅ 정답:

    import os
    
    filepath = "config.ini"
    if not os.path.exists(filepath):
        print("파일이 존재하지 않아 열 수 없습니다.")
    else:
        with open(filepath, "r") as f:
            print(f.read())
    

    🔍 해설: os.path.exists()를 이용하면 파일 존재 여부를 사전에 확인할 수 있어 open() 실패를 방지합니다.


    📝 문제1] 디렉토리가 없으면 자동 생성하고, 파일을 안전하게 쓰는 로직을 작성하세요.

    🖨️ 출력결과:

    파일 저장 완료
    

    ✅ 정답:

    import os
    
    folder = "logs"
    os.makedirs(folder, exist_ok=True)
    
    try:
        with open(f"{folder}/output.log", "w") as f:
            f.write("테스트 로그")
        print("파일 저장 완료")
    except OSError:
        print("파일 저장 실패")
    

    🔍 해설: os.makedirs(..., exist_ok=True)는 폴더가 없으면 생성하고, 있으면 무시합니다.


    📝 문제1] 외부 API 요청 중 ConnectionError 발생 시 사용자에게 안내 메시지를 출력하세요.

    🖨️ 출력결과:

    서버에 연결할 수 없습니다.
    

    ✅ 정답:

    import requests
    
    try:
        response = requests.get("https://invalid.api.url")
    except requests.exceptions.ConnectionError:
        print("서버에 연결할 수 없습니다.")
    

    🔍 해설: ConnectionError는 잘못된 URL 또는 서버 응답 불가 시 발생합니다.


    📝 문제1] API 응답이 너무 늦어 Timeout이 발생할 경우를 처리하세요.

    🖨️ 출력결과:

    요청 시간이 초과되었습니다.
    

    ✅ 정답:

    import requests
    
    try:
        requests.get("https://httpbin.org/delay/10", timeout=3)
    except requests.exceptions.Timeout:
        print("요청 시간이 초과되었습니다.")
    

    🔍 해설: timeout=초 옵션으로 요청 제한 시간을 설정하고, Timeout 예외로 처리합니다.


    📝 문제1] 리스트에서 인덱스를 잘못 참조할 때 발생하는 예외를 처리하세요.

    🖨️ 출력결과:

    잘못된 인덱스 접근입니다.
    

    ✅ 정답:

    data = [1, 2, 3]
    
    try:
        print(data[5])
    except IndexError:
        print("잘못된 인덱스 접근입니다.")
    

    🔍 해설: IndexError는 리스트 인덱스를 벗어난 접근에서 발생합니다.


    📝 문제1] 딕셔너리에서 없는 키를 참조할 경우 발생하는 예외를 처리하세요.

    🖨️ 출력결과:

    해당 키가 존재하지 않습니다.
    

    ✅ 정답:

    user = {"id": 1}
    
    try:
        print(user["name"])
    except KeyError:
        print("해당 키가 존재하지 않습니다.")
    

    🔍 해설: 딕셔너리에서 없는 키 접근은 KeyError를 발생시킵니다.


    📝 문제1] 클래스 객체에 없는 속성에 접근하여 AttributeError가 발생하는 코드를 처리하세요.

    🖨️ 출력결과:

    존재하지 않는 속성입니다.
    

    ✅ 정답:

    class User:
        def __init__(self, name):
            self.name = name
    
    u = User("홍길동")
    
    try:
        print(u.email)
    except AttributeError:
        print("존재하지 않는 속성입니다.")
    

    🔍 해설: 클래스 인스턴스에 없는 속성 접근 시 AttributeError가 발생합니다.


    📝 문제1] 함수가 아닌 객체를 호출하려고 하면 TypeError가 발생합니다. 이 예외를 처리하세요.

    🖨️ 출력결과:

    이 객체는 함수처럼 호출할 수 없습니다.
    

    ✅ 정답:

    class Config:
        def __init__(self):
            self.api_key = "abc"
    
    cfg = Config()
    
    try:
        cfg()  # 함수처럼 호출했지만 객체임
    except TypeError:
        print("이 객체는 함수처럼 호출할 수 없습니다.")
    

    🔍 해설: 객체는 __call__()을 정의하지 않으면 호출 불가 → TypeError 발생


    🔹 예외 발생 가능 지점 분석

    ◽ 사용자 입력 처리 사용자로부터 데이터를 입력받을 때는 항상 입력값이 유효한지 검사해야 합니다.
    입력값이 예상한 형식이나 범위를 벗어날 경우 프로그램이 오류를 내고 종료될 수 있으므로, 사전에 예외를 처리하는 것이 필수입니다.

    </> 예시코드: 숫자 유효성 검사 (ValueError, 음수 예외)

    def get_product_count():
        try:
            count = int(input("상품 개수를 입력하세요: "))
            if count < 0:
                raise ValueError("상품 개수는 0 이상이어야 합니다.")
            print(f"총 {count}개의 상품이 등록되었습니다.")
        except ValueError as e:
            print(f"입력 오류: {e}")
    
    get_product_count()
    

    ✔️ 의사코드:

    함수 get_product_count():
        사용자로부터 상품 개수를 입력받음
        숫자가 아니거나 음수이면 예외 발생
        정상적인 경우 등록 완료 메시지 출력
    

    🖨️ 출력결과:

    # (예: "세 개" 입력 시)
    입력 오류: invalid literal for int() with base 10: '세 개'
    
    # (예: -5 입력 시)
    입력 오류: 상품 개수는 0 이상이어야 합니다.
    

    🔍 해설:

    • int() 변환 실패 시 ValueError 발생
    • 음수일 경우 개발자가 직접 raise ValueError()로 예외 발생
    • 실무에서 재고 수량, 나이, 가격 등 숫자 입력이 필요한 필드에 자주 사용

    </> 예시코드: 선택지 유효성 검사 (ValueError, 범위 외 입력)

    def choose_menu():
        try:
            menu = input("메뉴 번호를 선택하세요 (1-3): ")
            if menu not in ["1", "2", "3"]:
                raise ValueError("유효한 메뉴 번호를 입력해주세요.")
            print(f"{menu}번 메뉴가 선택되었습니다.")
        except ValueError as e:
            print(f"❌ 선택 오류: {e}")
    
    choose_menu()
    

    ✔️ 의사코드:

    함수 choose_menu():
        사용자로부터 메뉴 번호 입력
        1, 2, 3 이외의 값이면 예외 발생
        정상 입력 시 선택된 메뉴 출력
    

    🖨️ 출력결과:

    선택 오류: 유효한 메뉴 번호를 입력해주세요.
    

    🔍 해설:

    • 입력값이 지정된 범위(1~3)에 포함되지 않으면 예외 처리
    • 실무에서는 옵션 선택, 서비스 항목 지정, 버튼 코드 처리 등에서 매우 흔하게 사용
    • raise를 통해 직접 커스텀 예외 메시지 출력 가능

    ✨ 실무팁:

    • 사용자 입력은 절대로 신뢰하지 말 것
    • 문자열, 숫자, 날짜 등 원하는 형식으로 변환하기 전에 반드시 검증
    • 예외를 처리하더라도 사용자에게 왜 문제가 발생했는지 구체적으로 알려주는 것이 중요

    while 루프를 사용한 재입력 유도 방식 (콘솔 환경) 사용자가 잘못된 값을 입력했을 경우, 에러 메시지를 보여주고 다시 입력을 유도하는 방식입니다. 콘솔 프로그램에서 자주 사용됩니다.

    </> 예시코드:

    def get_valid_age():
        while True:
            try:
                age = int(input("나이를 입력하세요 (정수만 가능): "))
                if age < 0:
                    raise ValueError("나이는 음수가 될 수 없습니다.")
                return age
            except ValueError as e:
                print(f"잘못된 입력: {e}")
    
    user_age = get_valid_age()
    print(f"입력된 나이: {user_age}")
    
    

    ✔️ 의사코드:

    
    

    🖨️ 출력결과:

    나이를 입력하세요 (정수만 가능): 스물
    잘못된 입력: invalid literal for int() with base 10: '스물'
    나이를 입력하세요 (정수만 가능): -5
    잘못된 입력: 나이는 음수가 될 수 없습니다.
    나이를 입력하세요 (정수만 가능): 25
    입력된 나이: 25
    

    🔍 해설:

    • while True 반복문으로 유효한 입력이 들어올 때까지 계속 시도
    • 사용자 입력을 검증하고 실패 시 친절한 메시지 제공

    ◽ GUI 기반 사용자 입력 검증 (예: Tkinter) GUI에서는 Entry(입력창)를 사용하고, 버튼을 눌렀을 때 입력값을 검증합니다.

    </> 예시코드: 숫자만 입력하도록 유도 (Tkinter)

    import tkinter as tk
    from tkinter import messagebox
    
    def check_input():
        try:
            age = int(entry.get())
            if age < 0:
                raise ValueError("나이는 음수가 될 수 없습니다.")
            messagebox.showinfo("입력 성공", f"입력된 나이: {age}")
        except ValueError as e:
            messagebox.showerror("입력 오류", str(e))
    
    root = tk.Tk()
    root.title("나이 입력")
    
    tk.Label(root, text="나이를 입력하세요:").pack()
    entry = tk.Entry(root)
    entry.pack()
    
    tk.Button(root, text="제출", command=check_input).pack()
    
    root.mainloop()
    

    ✔️ 의사코드:

    
    

    🖨️ 출력결과:

    
    

    🔍 해설:

    • GUI 입력값도 get()으로 받아서 숫자 검증 후 예외 발생 시 팝업으로 안내
    • 실무에서는 Tkinter, PyQt, Kivy 등에서 유사한 패턴 사용

    ◽ 웹 기반 사용자 입력 검증 (예: Django 폼) 웹에서는 HTML 입력폼과 서버 사이에서 백엔드에서 값 검증을 처리합니다.

    </> Django 예시: 유효성 검사 폼 만들기

    # forms.py
    from django import forms
    
    class AgeForm(forms.Form):
        age = forms.IntegerField(min_value=0, label="나이")
    
    # views.py
    from django.shortcuts import render
    from .forms import AgeForm
    
    def input_view(request):
        if request.method == "POST":
            form = AgeForm(request.POST)
            if form.is_valid():
                age = form.cleaned_data["age"]
                return render(request, "success.html", {"age": age})
        else:
            form = AgeForm()
        return render(request, "form.html", {"form": form})
    
    <!-- form.html -->
    <form method="post">
      {% csrf_token %}
      {{ form.as_p }}
      <button type="submit">제출</button>
    </form>
    

    🖨️ 출력결과:

    - 음수 또는 문자열 입력 시: "0 이상의 숫자여야 합니다" 자동 오류 메시지 표시
        
    - 성공 시: success.html 페이지로 이동하여 결과 출력
    

    🔍 해설:

    • Django의 forms는 서버단에서 입력값의 타입, 범위 등을 자동으로 검증
    • 사용자에게 오류 메시지를 보여주고, 잘못된 값은 다시 입력하도록 유도

    ◽ 파일/DB 연결 시점 파일이나 데이터베이스에 접근할 때, 다음과 같은 예외가 발생할 수 있습니다: - 파일이 존재하지 않음 → FileNotFoundError - 접근 권한이 없음 → PermissionError - DB 서버에 연결할 수 없음 → ConnectionError, OperationalError (예: SQLite, MySQL) - 경로 또는 포트 오류 → OSError, TimeoutError

    이러한 문제는 주로 초기 연결 시점에서 발생하며, 사용자에게 미리 
    경고하고 프로그램을 안전하게 종료하거나 재시도할 수 있도록 처리해야
    합니다.
    

    </> 예시코드: 파일 존재/권한 체크 (FileNotFoundError, PermissionError)

    def read_log_file(filepath):
        try:
            with open(filepath, "r", encoding="utf-8") as f:
                print("로그 파일 내용:")
                print(f.read())
        except FileNotFoundError:
            print(f"파일이 존재하지 않습니다: {filepath}")
        except PermissionError:
            print(f"읽기 권한이 없습니다: {filepath}")
    
    read_log_file("/etc/secure_log.txt")
    

    ✔️ 의사코드:

    함수 read_log_file(경로):
        파일을 읽기 모드로 열기 시도
        파일이 없으면 FileNotFoundError 발생
        권한이 없으면 PermissionError 발생
        정상적으로 열리면 내용 출력
    

    🖨️ 출력결과:

    # (예: 파일 없음)
    파일이 존재하지 않습니다: /etc/secure_log.txt
    
    # (예: 권한 없음)
    읽기 권한이 없습니다: /etc/secure_log.txt
    

    🔍 해설:

    • 파일 접근 전 항상 존재 여부와 권한 체크 필요
    • 실무에서는 설정 파일, 로그 파일, 환경 변수 로딩 시 사용

    </> 예시코드: SQLite DB 연결 예외 (OperationalError)

    import sqlite3
    
    def connect_database(db_path):
        try:
            conn = sqlite3.connect(db_path)
            print("DB 연결 성공")
            conn.close()
        except sqlite3.OperationalError as e:
            print(f"DB 연결 실패: {e}")
    
    connect_database("/invalid_path/app.db")
    

    ✔️ 의사코드:

    함수 connect_database(DB 경로):
        SQLite 데이터베이스 연결 시도
        경로 오류나 권한 문제 발생 시 OperationalError 처리
        연결 성공 시 확인 메시지 출력
    

    🖨️ 출력결과:

    DB 연결 실패: unable to open database file
    

    🔍 해설:

    • SQLite는 파일 기반 DB이므로 잘못된 경로나 권한 문제로 OperationalError 발생
    • 실무에서는 DB 연결 전 경로 체크, 백업 폴더 확인 등과 함께 사용
    • MySQL, PostgreSQL과 같이 서버 기반 DB의 경우 ConnectionError, TimeoutError 등도 함께 처리

    ✨ 실무 팁: FileNotFoundError 발생조건: 잘못된 파일 경로 대응방법: 존재 여부 확인 (os.path.exists)

    PermissionError 발생조건: 읽기/쓰기 권한 없음 대응방법: 권한 요청 or 관리자 권한 실행 안내

    OperationalError 발생조건: SQLite 또는 DB 연결 실패 대응방법: 경로, 권한, 락(lock) 상태 확인

    ConnectionError 발생조건: MySQL/PostgreSQL 연결 실패 대응방법: DB 서버 주소, 포트, 인증 정보 확인


    📝 문제1] 사용자에게 정수를 입력받아 출력하세요. 숫자가 아닌 값을 입력하면 예외를 처리하세요.

    🖨️ 출력결과:

    숫자를 입력해주세요.
    

    ✅ 정답:

    try:
        num = int(input("숫자를 입력하세요: "))
        print(f"입력된 숫자: {num}")
    except ValueError:
        print("❌ 숫자를 입력해주세요.")
    

    🔍 해설: int() 변환에서 예외 발생 가능성 (ValueError)을 try-except로 처리함.


    📝 문제2] 입력된 값이 0보다 작으면 ValueError를 강제로 발생시키고, 그 예외를 처리하세요.

    🖨️ 출력결과:

    음수는 허용되지 않습니다.
    

    ✅ 정답:

    try:
        value = int(input("양의 정수를 입력하세요: "))
        if value < 0:
            raise ValueError("음수는 허용되지 않습니다.")
    except ValueError as e:
        print(f"{e}")
    

    🔍 해설: 조건에 따라 raise로 강제 예외 발생 → as e로 메시지 출력.


    📝 문제3] 정수를 입력받을 때까지 계속 입력을 요구하세요. 올바른 값이 들어오면 종료합니다.

    🖨️ 출력결과:

    정수를 입력하세요: abc
    잘못된 입력입니다.
    정수를 입력하세요: 10
    입력된 정수: 10
    

    ✅ 정답:

    while True:
        try:
            x = int(input("정수를 입력하세요: "))
            print(f"입력된 정수: {x}")
            break
        except ValueError:
            print("잘못된 입력입니다.")
    

    🔍 해설: while True 루프와 break를 이용해 유효한 값이 나올 때까지 반복.


    📝 문제4] 0 이상의 실수를 입력받을 때까지 반복하세요. 잘못된 값 또는 음수는 다시 입력하게 하세요.

    🖨️ 출력결과:

    실수를 입력하세요: -3.2
    0 이상의 실수만 입력하세요.
    실수를 입력하세요: abc
    형식이 잘못되었습니다.
    실수를 입력하세요: 2.5
    입력 완료: 2.5
    

    ✅ 정답:

    while True:
        try:
            val = float(input("실수를 입력하세요: "))
            if val < 0:
                print("0 이상의 실수만 입력하세요.")
                continue
            print(f"입력 완료: {val}")
            break
        except ValueError:
            print("형식이 잘못되었습니다.")
    

    🔍 해설: 숫자 형식과 범위를 동시에 검증하며, 각각 다른 메시지 제공.


    📝 문제5] Tkinter를 이용해 숫자만 입력받는 버튼 이벤트를 구성하세요.
    숫자가 아니면 메시지 박스로 오류 안내를 하세요.

    🖨️ 출력결과: 팝업

    숫자가 아닙니다.
    

    ✅ 정답:

    import tkinter as tk
    from tkinter import messagebox
    
    def validate():
        try:
            val = int(entry.get())
            messagebox.showinfo("입력 완료", f"입력값: {val}")
        except ValueError:
            messagebox.showerror("입력 오류", "숫자가 아닙니다.")
    
    root = tk.Tk()
    entry = tk.Entry(root)
    entry.pack()
    tk.Button(root, text="확인", command=validate).pack()
    root.mainloop()
    

    🔍 해설: GUI 입력도 문자열 → 정수 변환 시 예외 발생 가능, try-except로 메시지 처리.


    📝 문제6] Tkinter로 만든 입력창에서 음수가 입력되면 ValueError를 강제로 발생시켜 처리하세요.

    🖨️ 출력결과:

    양수만 입력 가능합니다.
    

    ✅ 정답:

    import tkinter as tk
    from tkinter import messagebox
    
    def check_input():
        try:
            num = int(entry.get())
            if num < 0:
                raise ValueError("양수만 입력 가능합니다.")
            messagebox.showinfo("성공", f"입력값: {num}")
        except Exception as e:
            messagebox.showerror("오류", f"❌ {e}")
    
    root = tk.Tk()
    entry = tk.Entry(root)
    entry.pack()
    tk.Button(root, text="제출", command=check_input).pack()
    root.mainloop()
    

    🔍 해설: 입력 값 검증 + 강제 예외 + GUI 오류 메시지 표시 → 실무 유효성 검증 패턴.


    📝 문제7] Django 폼을 이용하여 나이를 정수로 입력받고, 0 미만 값은 오류가 되도록 하세요.

    🖨️ 출력결과:

    Enter a whole number.
    Ensure this value is greater than or equal to 0.
    

    ✅ 정답:

    from django import forms
    
    class AgeForm(forms.Form):
        age = forms.IntegerField(min_value=0, label="나이")
    

    🔍 해설: IntegerField(min_value=0)을 사용하면 음수 입력 시 자동으로 유효성 검사 오류 발생.


    📝 문제8] Django에서 이메일 필드를 가진 폼을 만들고, 올바르지 않은 형식 입력 시 오류를 발생시켜 보세요.

    🖨️ 출력결과:

    Enter a valid email address.
    

    ✅ 정답:

    from django import forms
    
    class EmailForm(forms.Form):
        email = forms.EmailField(label="이메일")
    

    🔍 해설: Django는 EmailField를 통해 이메일 형식 검증을 자동 처리함.


    📝 문제9] 존재하지 않는 파일을 열려 할 때 FileNotFoundError를 처리하세요.

    🖨️ 출력결과:

    파일을 찾을 수 없습니다.
    

    ✅ 정답:

    try:
        with open("unknown.txt", "r") as f:
            print(f.read())
    except FileNotFoundError:
        print("파일을 찾을 수 없습니다.")
    

    🔍 해설: open() 함수에서 파일이 없을 경우 FileNotFoundError 발생


    📝 문제10] SQLite 데이터베이스 연결 실패(OperationalError)를 처리하는 코드를 작성하세요.

    🖨️ 출력결과:

    데이터베이스 연결 실패
    

    ✅ 정답:

    import sqlite3
    
    try:
        conn = sqlite3.connect("/invalid/path/db.sqlite3")
    except sqlite3.OperationalError:
        print("데이터베이스 연결 실패")
    

    🔍 해설: 잘못된 경로 또는 권한 문제로 SQLite 연결이 실패할 수 있으며, OperationalError로 처리합니다.


    ◽ 외부 API 호출

    • requests.exceptions.ConnectionError: 서버에 연결하지 못했을 때
    • requests.exceptions.Timeout: 응답 시간이 너무 오래 걸릴 때
    • requests.exceptions.HTTPError: 404, 500 등 HTTP 오류 상태 코드가 반환될 때
    • requests.exceptions.RequestException: 위 모든 예외를 포함하는 최상위 예외

    </> 예시코드: 연결 실패 및 타임아웃 처리

    import requests
    
    def get_weather():
        try:
            response = requests.get("https://invalid-api.example.com/weather", timeout=5)
            response.raise_for_status()
            print(response.json())
        except requests.exceptions.ConnectionError:
            print("서버에 연결할 수 없습니다.")
        except requests.exceptions.Timeout:
            print("요청 시간이 초과되었습니다.")
        except requests.exceptions.RequestException as e:
            print(f"기타 예외 발생: {e}")
    
    get_weather()
    

    ✔️ 의사코드:

    함수 get_weather():
        API 요청 전송
        연결 실패 시 ConnectionError 처리
        응답 지연 시 Timeout 처리
        기타 오류는 RequestException으로 처리
    

    🖨️ 출력결과:

    # 서버주소오류
    서버에 연결할 수 없습니다.
    
    # 응답 지연
    요청 시간이 초과되었습니다.
    

    🔍 해설:

    • timeout=5는 5초 안에 응답이 없으면 예외를 발생시킴
    • raise_for_status()를 통해 400 이상 응답도 잡을 수 있음
    • 실무에서 매우 일반적인 API 통신 안정성 확보 방식

    </> 예시코드: 응답 코드 오류 (HTTPError) 처리

    import requests
    
    def fetch_user_profile():
        try:
            response = requests.get("https://jsonplaceholder.typicode.com/users/999")  # 없는 사용자
            response.raise_for_status()  # 404 에러 발생
            data = response.json()
            print("사용자 정보:", data)
        except requests.exceptions.HTTPError as e:
            print(f"서버 응답 오류 (HTTP 상태): {e.response.status_code}")
        except requests.exceptions.RequestException as e:
            print(f"예외 발생: {e}")
    
    fetch_user_profile()
    

    ✔️ 의사코드:

    함수 fetch_user_profile():
        사용자 정보를 API로 요청
        상태 코드가 4xx/5xx이면 raise_for_status()가 HTTPError 발생
        상태 코드 확인 후 사용자에게 메시지 출력
    

    🖨️ 출력결과:

    서버 응답 오류 (HTTP 상태): 404
    

    🔍 해설:

    • 응답 상태 코드가 400 이상이면 raise_for_status()HTTPError 예외 발생
    • 실무에서 리소스 없음(404), 인증 실패(401), 서버 에러(500) 등 오류 코드별 분기 처리 가능
    • 백엔드 API 통합, 파트너사 API 연동, 프론트 백엔드 통신 등 다양한 환경에 사용됨

    ◽ 반복문 / 컬렉션 접근 컬렉션(리스트, 딕셔너리 등)을 순회하거나 특정 요소를 접근할 때 다음과 같은 예외가 발생할 수 있습니다: - IndexError: 리스트, 튜플 등에서 존재하지 않는 인덱스를 참조했을 때 - KeyError: 딕셔너리에서 존재하지 않는 키로 접근했을 때

    이는 반복문이나 조건문을 통해 데이터를 처리할 때 실수로 발생할 수 
    있으며, 실무에서는 데이터 파싱, API 응답 처리, 폼 값 조회 등에서 
    매우 빈번하게 발생합니다.
    

    </> 예시코드: IndexError – 리스트 인덱스 초과

    def print_top_5_items(items):
        try:
            for i in range(5):
                print(f"{i+1}위: {items[i]}")
        except IndexError as e:
            print(f"인덱스 오류: {e}")
    
    item_list = ["사과", "바나나", "포도"]  # 3개만 존재
    print_top_5_items(item_list)
    

    ✔️ 의사코드:

    함수 print_top_5_items(리스트):
        0부터 4까지 순회하면서 리스트 요소 출력
        리스트 길이가 5보다 작으면 IndexError 발생
    

    🖨️ 출력결과:

    1위: 사과
    2위: 바나나
    3위: 포도
    인덱스 오류: list index out of range
    

    🔍 해설:

    • 리스트에 존재하지 않는 인덱스를 접근하여 IndexError 발생
    • 실무에서는 데이터 개수와 루프 범위를 일치시키거나 len() 조건문을 사용하여 방지

    </> 예시코드: KeyError – 딕셔너리 키 누락

    def show_user_info(user):
        try:
            print(f"이름: {user['name']}")
            print(f"이메일: {user['email']}")
        except KeyError as e:
            print(f"키 오류: '{e}' 필드가 누락되었습니다.")
    
    user_data = {
        "name": "홍길동"
        # 'email' 키는 없음
    }
    
    show_user_info(user_data)
    

    ✔️ 의사코드:

    함수 show_user_info(딕셔너리):
        'name''email' 키로 값 출력
        'email' 키가 없으면 KeyError 발생
    

    🖨️ 출력결과:

    이름: 홍길동
    키 오류: 'email' 필드가 누락되었습니다.
    

    🔍 해설:

    • 존재하지 않는 키에 접근했기 때문에 KeyError 발생
    • 실무에서는 .get()으로 안전하게 접근하거나 in 키워드로 사전 검사 필요

    ✨ 실무팁: IndexError 원인: 리스트 인덱스 초과 접근 안전한 처리 방식 예시: if i < len(list):, try-except

    KeyError 원인: 딕셔너리에 존재하지 않는 키 접근 안전한 처리 방식 예시: dict.get('key', '기본값'), if 'key' in dict:


    ◽ 복잡한 중첩 구조에서의 예외 처리

    # 중첩구조의 데이터
    users = [
        {"name": "홍길동", "email": "hong@example.com"},
        {"name": "김영희"},  # email 없음
        "잘못된 데이터",     # 딕셔너리가 아님
    ]
    
    리스트 안에 딕셔너리, 또는 예상과 다른 타입이 혼합된 구조에서는
    다음과 같은 예외가 발생할 수 있습니다:
    	- IndexError: 리스트 인덱스 범위 초과
    	- KeyError: 딕셔너리에 없는 키 접근
    	- TypeError`: 딕셔너리가 아닐 때 .get()이나 ['key'] 접근 시
    

    </> 예시코드: 사용자 리스트 처리 (복합 예외 대응)

    users = [
        {"name": "홍길동", "email": "hong@example.com"},
        {"name": "김영희"},  # email 없음
        "잘못된 데이터",     # dict 아님
        {"email": "no_name@example.com"}  # name 없음
    ]
    
    def print_user_list(users):
        for i, user in enumerate(users):
            try:
                if not isinstance(user, dict):
                    raise TypeError("사용자정보는 딕셔너리형태여야합니다")
                name = user.get("name", "이름 없음")
                email = user["email"]  # KeyError 가능성
                print(f"{i+1}번째 사용자: {name} ({email})")
            except KeyError as e:
                print(f"[{i+1}] 누락된 키: {e}")
            except TypeError as e:
                print(f"[{i+1}] 잘못된 데이터 형식: {e}")
    
    print_user_list(users)
    

    ✔️ 의사코드:

    사용자 리스트를 순회하며:
        딕셔너리인지 확인 (아니면 TypeError 발생)
        'name'은 .get()으로 기본값 제공
        'email'은 반드시 있어야 하므로 KeyError 가능
        예외에 따라 알맞은 메시지 출력
    

    🖨️ 출력결과:

    1번째 사용자: 홍길동 (hong@example.com)
    [2] 누락된 키: 'email'
    [3] 잘못된 데이터 형식: 사용자 정보는 딕셔너리 형태여야 합니다.
    [4] 누락된 키: 'email'
    

    🔍 해설:

    • get()을 사용하여 KeyError 위험이 있는 "name" 필드는 기본값 제공
    • user["email"] 은 반드시 존재해야 하므로 KeyError 발생 가능
    • 문자열인 "잘못된 데이터".get()을 지원하지 않아서 TypeError 발생
    • 실무에서는 API 응답, CSV 파싱, JSON 데이터 등에서 이러한 예외 처리를 필수적으로 구현

    ◽ 형변환 시도 프로그래밍에서는 문자열, 숫자, 불리언 등 데이터 타입을 변환(type casting)하는 일이 흔합니다.
    하지만 아래와 같은 경우에는 예외가 발생할 수 있습니다:

    - int("abc") → ValueError: 숫자로 변환할 수 없는 문자열
    - float(None) → TypeError: 타입 자체가 지원되지 않음
    - str(list) → 가능하지만 예상과 다른 결과가 나올 수 있음
    
    형변환은 주로 사용자 입력, API 응답, 엑셀/CSV 데이터 처리 시
    사용되며, 반드시 유효성 검사와 함께 예외 처리가 필요합니다.
    

    </> 예시코드: 문자열 → 정수 (ValueError)

    def parse_quantity(value):
        try:
            quantity = int(value)
            print(f"주문 수량: {quantity}개")
        except ValueError as e:
            print(f"형변환 오류: {e}")
    
    parse_quantity("10")     # 정상
    parse_quantity("열 개")  # 오류
    

    ✔️ 의사코드:

    문자열 입력을 정수로 변환 시도
    문자형 숫자는 int로 변환됨
    문자가 섞인 문자열은 ValueError 발생
    

    🖨️ 출력결과:

    주문 수량: 10개
    형변환 오류: invalid literal for int() with base 10: '열 개'
    

    🔍 해설:

    • "10"은 정수로 변환 가능하지만 "열 개"는 불가능하여 ValueError 발생
    • 실무에서는 주문 수량, 나이, 점수 등 숫자 입력 필드에서 반드시 필요한 처리

    </> 예시코드: None 또는 리스트 → 실수 (TypeError, ValueError)

    def convert_price(data):
        try:
            price = float(data)
            print(f"가격: {price}원")
        except (ValueError, TypeError) as e:
            print(f"형변환 실패: {e}")
    
    convert_price("1000.50")     # 정상
    convert_price(None)          # TypeError
    convert_price("천 원")       # ValueError
    

    ✔️ 의사코드:

    입력값을 실수(float)로 변환 시도
    None → TypeError 발생
    문자 포함 문자열 → ValueError 발생
    정상 값은 가격으로 출력
    

    🖨️ 출력결과:

    가격: 1000.5원
    형변환 실패: float() argument must be a string or a number, not 'NoneType'
    형변환 실패: could not convert string to float: '천 원'
    

    🔍 해설:

    • float(None)TypeError: 타입이 아예 안 맞는 경우
    • float("천 원")ValueError: 형식이 맞지 않는 문자열
    • 실무에서는 가격, 비율, 환율 등의 필드에서 매우 자주 등장
    • isinstance() + try-except 조합이 안정적인 처리 방식

    ◽ 함수 호출 및 리턴 처리 함수를 호출하거나 반환값을 처리할 때, 다음과 같은 예외가 발생할 수 있습니다: - AttributeError / TypeError (특히 NoneType) → 함수가 None을 반환했는데, 이를 객체처럼 .메서드()[인덱스]로 접근할 경우

    - 반환값이 있는지 확인하지 않고 바로 사용하는 습관은  
        "NoneType object has no attribute ..." 같은 오류로 이어질
        수 있습니다.
    

    </> 예시코드: 함수가 None을 반환했을 때 .strip() 호출 → AttributeError

    def get_username(user):
        return user.get("name")  # name이 없을 수도 있음
    
    user1 = {"email": "abc@example.com"}  # 'name' 없음
    
    try:
        name = get_username(user1).strip()
        print(f"사용자 이름: {name}")
    except AttributeError as e:
        print(f"❌ 오류 발생: {e}")
    

    ✔️ 의사코드:

    딕셔너리에서 'name' 값을 꺼내오는 함수 정의
    name이 없으면 None을 반환
    None.strip()을 호출하면 AttributeError 발생
    

    🖨️ 출력결과:

    오류 발생: 'NoneType' object has no attribute 'strip'
    

    🔍 해설:

    • user.get("name")None일 경우 .strip() 호출 시 오류
    • 실무에서는 API 응답, 사용자 정보 접근 시 매우 흔한 실수
    • 해결책: if value is not None: 또는 value or '' 처리

    </> 예시코드: 함수 리턴값이 None일 때 인덱싱 시도 → TypeError

    def find_first_item(items):
        if items:
            return items[0]
        # return 생략 시 암묵적으로 None 반환됨
    
    try:
        item = find_first_item([])[0]  # None[0] 호출
        print(f"첫 번째 아이템: {item}")
    except TypeError as e:
        print(f"호출 오류: {e}")
    

    ✔️ 의사코드:

    리스트가 비어 있을 경우 아무것도 반환하지 않는 함수 정의
    함수 결과가 None이면 [0] 접근 시 TypeError 발생
    

    🖨️ 출력결과:

    호출 오류: 'NoneType' object is not subscriptable
    

    🔍 해설:

    • 빈 리스트일 경우 return이 생략되어 함수가 None을 반환
    • 이후 [0] 접근하려다 TypeError 발생
    • 실무에서는 검색 결과 처리, 필터링 로직 등에서 자주 발생

    📝 문제1] 외부 API 요청 중 서버에 연결할 수 없을 때 ConnectionError를 처리하세요.

    🖨️ 출력결과:

    API 서버에 연결할 수 없습니다.
    

    ✅ 정답코드:

    import requests
    
    try:
        response = requests.get("https://invalid.api.example.com")
    except requests.exceptions.ConnectionError:
        print("API 서버에 연결할 수 없습니다.")
    

    🔍 코드해설: 잘못된 URL이나 서버 오류로 인해 연결이 되지 않으면 ConnectionError가 발생합니다.


    📝 문제2] API 요청이 시간 초과로 실패할 경우, 이를 Timeout 예외로 처리하세요.

    🖨️ 출력결과:

    요청 시간이 초과되었습니다.
    

    ✅ 정답코드:

    import requests
    
    try:
        requests.get("https://httpbin.org/delay/10", timeout=3)
    except requests.exceptions.Timeout:
        print("요청 시간이 초과되었습니다.")
    

    🔍 코드해설: timeout=3은 3초 안에 응답이 없으면 Timeout 예외 발생


    📝 문제3] 존재하지 않는 리스트 인덱스를 접근할 때 발생하는 예외를 처리하세요.

    🖨️ 출력결과:

    잘못된 인덱스입니다.
    

    ✅ 정답코드:

    nums = [1, 2, 3]
    
    try:
        print(nums[5])
    except IndexError:
        print("❌ 잘못된 인덱스입니다.")
    

    🔍 코드해설: IndexError는 리스트, 튜플에서 인덱스를 벗어났을 때 발생합니다.


    📝 문제4] 딕셔너리에서 존재하지 않는 키를 접근했을 때 발생하는 예외를 처리하세요.

    🖨️ 출력결과:

    키가 존재하지 않습니다.
    

    ✅ 정답코드:

    user = {"name": "Alice"}
    
    try:
        print(user["email"])
    except KeyError:
        print("키가 존재하지 않습니다.")
    

    🔍 코드해설: 딕셔너리에서 없는 키를 조회하면 KeyError가 발생합니다.


    📝 문제5] 딕셔너리 리스트에서 누락된 키와 잘못된 타입을 모두 처리하는 코드를 작성하세요.

    🖨️ 출력결과:

    [2] 잘못된 사용자 정보 형식
    [3] 누락된 키: 'email'
    

    ✅ 정답코드:

    users = [
        {"name": "홍길동", "email": "hong@test.com"},
        "문자열로 된 데이터",
        {"name": "김영희"}
    ]
    
    for i, user in enumerate(users, start=1):
        try:
            if not isinstance(user, dict):
                raise TypeError("잘못된 사용자 정보 형식")
            print(f"{user['name']} - {user['email']}")
        except KeyError as e:
            print(f"[{i}] 누락된 키: {e}")
        except TypeError as e:
            print(f"[{i}] {e}")
    
    

    🔍 코드해설: 중첩된 리스트 내 데이터의 구조와 키 유무를 동시에 검사하고, 예외 메시지를 출력합니다.


    📝 문제6] 중첩된 리스트+딕셔너리 구조에서 리스트 인덱스와 키 오류를 각각 분리하여 처리하세요.

    🖨️ 출력결과:

    인덱스 오류 발생
    키 오류 발생
    

    ✅ 정답코드:

    data = [{"id": 1}, {"id": 2}]
    try:
        item = data[5]  # IndexError
        print(item["name"])  # KeyError
    except IndexError:
        print("인덱스 오류 발생")
    except KeyError:
        print("키 오류 발생")
    

    🔍 코드해설: 리스트 접근 오류(IndexError)와 키 접근 오류(KeyError)를 각각 분리해서 처리함.


    📝 문제7] 입력된 문자열이 숫자가 아니면 ValueError가 발생하도록 하고, 이를 처리하세요.

    🖨️ 출력결과:

    숫자가 아닙니다.
    

    ✅ 정답코드:

    try:
        num = int("abc")
    except ValueError:
        print("숫자가 아닙니다.")
    

    🔍 코드해설: int() 함수는 숫자 형식이 아닌 문자열에 대해 ValueError를 발생시킵니다.


    📝 문제8] None이나 리스트를 float()으로 변환할 수 없을 때 예외를 처리하세요.

    🖨️ 출력결과:

    변환할 수 없는 데이터입니다.
    

    ✅ 정답코드:

    def convert(data):
        try:
            return float(data)
        except (ValueError, TypeError):
            print("변환할 수 없는 데이터입니다.")
    
    convert(None)
    convert([1, 2])
    

    🔍 코드해설: float()str, int, float만 허용 → TypeError, ValueError를 함께 처리


    📝 문제9] None을 반환한 함수의 결과에 .strip()을 호출하여 예외가 발생하는 경우를 처리하세요.

    🖨️ 출력결과:

    문자열이 아닙니다.
    

    ✅ 정답코드:

    def get_name(data):
        return data.get("name")
    
    user = {"email": "abc@test.com"}
    
    try:
        print(get_name(user).strip())
    except AttributeError:
        print("문자열이 아닙니다.")
    

    🔍 코드해설: get()이 반환한 None.strip()을 호출하면 AttributeError 발생


    📝 문제10] 리턴값이 None일 수 있는 함수를 호출한 후, 인덱싱을 시도할 때 발생할 수 있는 예외를 처리하세요.

    🖨️ 출력결과:

    함수 반환값이 유효하지 않습니다.
    

    ✅ 정답코드:

    def get_items():
        return None  # 조건에 따라 None 반환
    
    try:
        result = get_items()[0]
    except TypeError:
        print("함수 반환값이 유효하지 않습니다.")
    

    🔍 코드해설: None[0]TypeError 발생 → 함수 리턴값을 검사하거나 예외로 감싸는 방식 필요


    🔹 모든 예외 처리 패턴

    ◽ 특정 예외 처리 except 예외타입: 구문은, 코드 실행 중 발생할 수 있는 특정한 예외만 골라서 처리하는 방식입니다. 이를 통해 프로그램이 예상치 못한 상황에서도 정상적으로 종료되지 않도록 안정성을 높일 수 있습니다.

    여러 종류의 예외가 발생할 수 있는 상황에서는 각 예외마다 
    except ValueError:, except TypeError:처럼 명확하게 분리하여 
    처리하는 것이 좋습니다.
    

    </> 예시코드: ValueError만 처리 (숫자 입력 검증)

    def get_price():
        try:
            price = int(input("상품 가격을 입력하세요: "))
            print(f"입력한 가격: {price}원")
        except ValueError:
            print("숫자 형식으로 입력해야 합니다.")
    
    get_price()
    

    ✔️ 의사코드:

    사용자에게 숫자 입력 받기
    정수로 변환 시도
    숫자가 아닌 경우 ValueError 발생 → 해당 예외만 처리
    

    🖨️ 출력결과: 예: “삼천” 입력 시

    숫자 형식으로 입력해야 합니다.
    

    🔍 해설:

    • 사용자가 숫자가 아닌 값을 입력했을 때 ValueError 발생
    • 해당 예외만 명확히 처리하여 입력 오류 상황에만 반응
    • 실무에서는 금액, 수량 등 정수 입력 필드에 필수적으로 사용됨

    </> 예시코드: FileNotFoundError만 처리 (파일 열기 실패)

    def read_config_file():
        try:
            with open("config/settings.ini", "r", encoding="utf-8") as f:
                print(f.read())
        except FileNotFoundError:
            print("설정 파일을 찾을 수 없습니다.")
    
    read_config_file()
    

    ✔️ 의사코드:

    파일 열기 시도
    파일이 존재하지 않으면 FileNotFoundError 발생
    해당 예외만 골라서 메시지 출력
    

    🖨️ 출력결과:

    설정 파일을 찾을 수 없습니다.
    

    🔍 해설:

    • FileNotFoundError는 파일 경로 오류 시 발생
    • 해당 예외만 처리함으로써 다른 예외(인코딩 오류 등)는 알려주고 종료 가능
    • 실무에서는 설정 파일, 로그 파일, CSV 로딩 시 매우 자주 사용

    ◽ 다중 예외 처리 다중 예외 처리는 하나의 except 블록에서 여러 개의 예외를 동시에 처리할 수 있는 구조입니다.

    except (TypeError, ValueError):
        # 두 예외 모두 처리
    
    이 구조는 유사한 원인으로 발생하는 예외를 한꺼번에 다루고자 할 때 
    유용합니다.  예를 들어, 사용자 입력에서 발생할 수 있는 타입 변환 
    오류와 값 오류를 하나의 로직에서 처리할 수 있습니다.
    

    </> 예시코드: 숫자 변환 오류 (ValueError, TypeError)

    def process_quantity(data):
        try:
            quantity = int(data)
            print(f"주문 수량: {quantity}개")
        except (ValueError, TypeError):
            print("❌ 유효하지 않은 수량입니다. 숫자를 입력해주세요.")
    
    process_quantity("5")        # 정상
    process_quantity("다섯")     # ValueError
    process_quantity(None)       # TypeError
    

    ✔️ 의사코드:

    입력값을 정수로 변환 시도
    문자열이 잘못됐거나 타입이 맞지 않으면 예외 발생
    두 예외를 한 번에 처리
    

    🖨️ 출력결과:

    주문 수량: 5개
    유효하지 않은 수량입니다. 숫자를 입력해주세요.
    유효하지 않은 수량입니다. 숫자를 입력해주세요.
    

    🔍 해설:

    • "다섯"ValueError, NoneTypeError
    • 두 경우 모두 동일한 메시지로 처리 가능 → 로직 단순화에 유리
    • 실무에서는 사용자 입력 검증, API 수신값 처리 등에서 자주 사용

    </> 예시코드: 리스트 + 딕셔너리 변환 오류 (TypeError, ValueError)

    def parse_discount(data):
        try:
            # 문자열을 정수로 변환해서 할인율 계산
            rate = int(data)
            print(f"할인율: {rate}%")
        except (ValueError, TypeError):
            print("할인율 입력이 잘못되었습니다. 숫자로 입력해주세요.")
    
    parse_discount("15")       # 정상
    parse_discount("열다섯")   # ValueError
    parse_discount([15])       # TypeError
    

    ✔️ 의사코드:

    입력값을 정수로 변환하여 할인율 계산
    문자열이 숫자가 아니거나, 리스트가 들어오면 예외 발생
    두 예외를 하나의 except로 처리
    

    🖨️ 출력결과:

    할인율: 15%
    할인율 입력이 잘못되었습니다. 숫자로 입력해주세요.
    할인율 입력이 잘못되었습니다. 숫자로 입력해주세요.
    

    🔍 해설:

    • 여러 타입의 잘못된 입력(str, list, None)을 하나의 예외 처리 블록에서 처리 가능
    • 입력 필드가 외부로부터 유입될 수 있는 다양한 타입일 경우 매우 유용

    ✨ 실무 팁:

    • 예외가 비슷한 맥락일 때만** 다중 예외 처리 사용 (ValueError, TypeError, KeyError 등)
    • except (예외1, 예외2) as e: 형식으로 예외 메시지 추출도 가능
    • 코드 가독성 유지 및 중복 처리 로직 최소화에 효과적

    ◽ 모든 예외 잡기 except Exception: 또는 except:는 발생 가능한 모든 예외를 한꺼번에 처리하는 방식입니다.

    - except Exception: → 대부분의 일반적인 런타임 오류를 처리 
    (모든 내장 예외 클래스의 부모)
        
    - except: → SystemExit, KeyboardInterrupt까지도 포함하는 모든 
    예외를 포괄적으로 처리 (지양)
    
    실무에서는 except Exception:을 로그 저장, 사용자 알림, 안정적 종료
    등에 활용  
    except:는 정말 필요한 경우에만 사용해야 하며, 디버깅을 어렵게 만들
    수 있음
    

    </> 예시코드: 모든 예외를 잡아 로그 남기기 (Exception)

    def calculate_ratio(x, y):
        try:
            result = x / y
            print(f"계산 결과: {result}")
        except Exception as e:
            print(f"예외 발생: {e}")
    
    calculate_ratio(10, 0)  # ZeroDivisionError
    

    ✔️ 의사코드:

    두 수를 나눈 결과 출력
    예외 발생 가능성 있음 (0으로 나누기 등)
    모든 예외를 except Exception으로 잡아 출력
    

    🖨️ 출력결과:

    예외 발생: division by zero
    

    🔍 해설:

    • ZeroDivisionError를 명시하지 않고도 모든 예외를 처리
    • 예외 메시지를 e로 받아 사용자 또는 개발자에게 알림
    • 실무에서는 간단한 계산기, 데이터 처리 앱 등에서 최소 안전장치로 활용

    </> 예시코드: 프로그램 중단 방지용 예외 처리 (except:)

    def run_script():
        try:
            # 오류 발생 가능 코드
            items = ["A", "B", "C"]
            print(items[5])  # IndexError
        except:
            print("오류가 발생했지만 프로그램은 계속 실행됩니다.")
    
    print("시작")
    run_script()
    print("종료")
    

    ✔️ 의사코드:

    리스트에서 잘못된 인덱스 접근
    모든 예외를 except: 로 잡아 메시지만 출력
    프로그램 흐름 유지
    

    🖨️ 출력결과:

    시작
    오류가 발생했지만 프로그램은 계속 실행됩니다.
    종료
    

    🔍 해설:

    • IndexError 발생에도 except:가 모든 예외를 잡고 흐름을 유지
    • 실무에서는 크리티컬하지 않은 백그라운드 작업에서 사용될 수 있음
    • 하지만 except:는 디버깅이 어려워지고 오류 은폐 위험이 있어 주의 필요

    ✨ 실무팁:

    • except Exception as e:
      → 일반적인 예외 처리에 가장 많이 사용, 로깅 또는 사용자 알림에 적합
    • except:
      → 예외 유형이 불분명할 때 사용하지만 정말 필요한 경우만 제한적으로 사용
    • 예외 처리 후 로그 기록, 사용자 메시지, 재시도 또는 안전한 종료 흐름을 반드시 설계

    📝 문제1] 사용자로부터 숫자를 입력받아 int()로 변환하세요.
    문자 입력 시 ValueError를 처리하여 에러 메시지를 출력하세요.

    🖨️ 출력결과:

    숫자를 입력해주세요.
    

    ✅ 정답:

    try:
        age = int(input("나이를 입력하세요: "))
    except ValueError:
        print("숫자를 입력해주세요.")
    

    🔍 해설: 숫자가 아닌 값을 int()로 변환하면 ValueError가 발생하며, 이를 특정 예외로 처리합니다.


    📝 문제2] 리스트에서 존재하지 않는 인덱스를 참조할 경우 IndexError만을 처리하도록 코드를 작성하세요.

    🖨️ 출력결과:

    인덱스를 벗어났습니다.
    

    ✅ 정답:

    nums = [1, 2, 3]
    try:
        print(nums[10])
    except IndexError:
        print("인덱스를 벗어났습니다.")
    

    🔍 해설: 특정한 예외 IndexError만 지정하여 발생 가능성 있는 부분만을 정확히 대응합니다.


    📝 문제3] 사용자로부터 값을 받아 float()으로 변환하세요.
    입력이 "None" 또는 "문자"일 경우 각각 TypeError 또는 ValueError가 발생합니다. 두 예외를 함께 처리하세요.

    🖨️ 출력결과:

    잘못된 입력입니다.
    

    ✅ 정답:

    def convert(value):
        try:
            return float(value)
        except (ValueError, TypeError):
            print("잘못된 입력입니다.")
    
    convert("문자")
    convert(None)
    

    🔍 해설: 여러 예외가 예상될 경우 (예외1, 예외2) 튜플 형태로 다중 예외 처리를 할 수 있습니다.


    📝 문제4] 파일을 열고 내용을 실수로 변환하는 과정을 하나의 블록에서 처리하세요. 발생 가능한 예외는 FileNotFoundError, ValueError, TypeError입니다.

    🖨️ 출력결과:

    예외 발생: 파일 열기 또는 변환 실패
    

    ✅ 정답:

    try:
        with open("input.txt", "r") as f:
            content = float(f.read())
    except (FileNotFoundError, ValueError, TypeError):
        print("예외 발생: 파일 열기 또는 변환 실패")
    

    🔍 해설: 파일 열기 및 변환에서 발생 가능한 예외를 다중 예외 처리로 묶어 관리하면 코드가 간결해집니다.


    📝 문제5] 예외 종류에 관계없이 모든 예외를 한 번에 잡아 처리하는 코드를 작성하세요.

    🖨️ 출력결과:

    오류가 발생했지만 프로그램은 종료되지 않았습니다.
    

    ✅ 정답:

    try:
        print(10 / 0)
    except:
        print("오류가 발생했지만 프로그램은 종료되지 않았습니다.")
    
    

    🔍 해설: except:는 예외 종류를 지정하지 않고 모든 예외를 포괄적으로 처리할 수 있습니다.
    실무에서는 디버깅 시 한시적으로 사용하며, 남발은 지양해야 합니다.


    📝 문제6] except Exception as e:를 사용하여 예외 메시지를 출력하세요.

    🖨️ 출력결과:

    예외 발생: division by zero
    

    ✅ 정답:

    try:
        result = 10 / 0
    except Exception as e:
        print(f"예외 발생: {e}")
    

    🔍 해설: Exception은 대부분의 내장 예외 클래스의 부모이며, as e를 사용하면 예외 메시지를 동적으로 출력할 수 있습니다.


    ◽ 예외 재발생 예외를 except 블록에서 처리한 후, 다시 상위 호출자에게 예외를 전달하고 싶을 때 raise를 사용합니다. - raise 는 현재 발생한 예외를 그대로 다시 발생시키거나, - 새롭게 정의한 예외로 변환하여 던질 수도 있습니다.`

    실무에서는 하위 함수에서 로그 또는 메시지를 기록한 후,  
    최종 처리는 상위에서 하도록 할 때 자주 사용됩니다.
    

    </> 예시코드: 하위 함수에서 처리 후 예외 재발생 (raise)

    def parse_age(value):
        try:
            return int(value)
        except ValueError as e:
            print(f"[로그] 잘못된 나이 입력: {value}")
            raise  # 예외를 다시 상위로 전달
    
    def main():
        try:
            age = parse_age("스물")
            print(f"나이: {age}")
        except ValueError:
            print("사용자에게 안내: 나이는 숫자로 입력해주세요.")
    
    main()
    

    ✔️ 의사코드:

    parse_age 함수:
        문자열을 정수로 변환 시도
        ValueError 발생 시 로그 출력 후 다시 raise
    
    main 함수:
        parse_age 호출
        예외 발생 시 사용자에게 안내 메시지 출력
    

    🖨️ 출력결과:

    [로그] 잘못된 나이 입력: 스물
    사용자에게 안내: 나이는 숫자로 입력해주세요.
    

    🔍 해설:

    • 하위 함수(parse_age)에서는 로깅만 하고,
    • 실제 예외 처리는 상위 함수(main)에서 담당 → 역할 분리

    </> 예시코드: 예외 변환 후 재발생 (raise CustomException)

    class ConfigLoadError(Exception):
        pass
    
    def load_config(path):
        try:
            with open(path, "r", encoding="utf-8") as f:
                return f.read()
        except FileNotFoundError:
            raise ConfigLoadError(f"설정 파일을 찾을 수 없습니다: {path}")
    
    try:
        load_config("missing.ini")
    except ConfigLoadError as e:
        print(f"설정 오류: {e}")
    

    ✔️ 의사코드:

    load_config 함수:
        파일 열기 시도
        FileNotFoundError 발생 시 → ConfigLoadError로 변환하여 재발생
    
    상위 호출자:
        ConfigLoadError 잡아서 사용자에게 출력
    

    🖨️ 출력결과:

    설정 오류: 설정 파일을 찾을 수 없습니다: missing.ini
    

    🔍 해설:

    • 하위에서 발생한 FileNotFoundError를 의미 있는 예외로 감싸서 상위에 전달
    • 실무에서는 API 예외, DB 연결 오류 등을 도메인 특화 예외로 감싸기 위해 사용

    ✨ 실무 팁:

    • raise는 예외를 다시 상위에 전달할 때 사용
    • 로깅은 하위에서, 처리 책임은 상위에서 → 역할 분리
    • raise NewError from e 문법으로 원래 예외 정보를 유지하면서 변환 가능

    ◽ 예외 로깅 logging 모듈은 Python의 표준 로깅 라이브러리로,
    예외 발생 시 파일이나 콘솔에 오류 메시지와 스택 트레이스(traceback)를 기록하는 데 사용됩니다.

    실무에서는 단순히 print()로 출력하지 않고,  
    logging.error() 또는 logging.exception()을 사용해 에러 로그를 
    기록하는 방식이 일반적입니다.
    

    </> 예시코드: logging.exception()으로 예외와 traceback 로깅

    import logging
    
    # 로그 설정
    logging.basicConfig(filename='error.log', level=logging.ERROR)
    
    def divide(x, y):
        try:
            return x / y
        except ZeroDivisionError:
            logging.exception("0으로 나누기 시도")
            print("계산 중 오류가 발생했습니다.")
    
    divide(10, 0)
    

    ✔️ 의사코드:

    divide 함수에서 0으로 나누는 오류 발생 가능
    예외 발생 시 logging.exception()으로 전체 트레이스백 로그 파일에 기록
    사용자에게는 친절한 메시지만 출력
    

    🖨️ 출력결과: 콘솔

    계산 중 오류가 발생했습니다.
    

    🖨️ 출력결과: 콘솔

    ERROR:root:0으로 나누기 시도
    Traceback (most recent call last):
      File "example.py", line 8, in divide
        return x / y
    ZeroDivisionError: division by zero
    

    🔍 해설:

    • logging.exception()은 현재 예외에 대한 전체 스택 트레이스를 자동 기록
    • 콘솔에는 메시지만 출력하고, 파일에는 상세 정보 기록 → 사용자 친화 + 개발자 디버깅 모두 충족

    </> 예시코드: 사용자 입력 오류를 logging.error()로 기록

    import logging
    
    # 콘솔 출력 및 파일 기록 설정
    logging.basicConfig(
        level=logging.ERROR,
        format='[%(asctime)s] %(levelname)s: %(message)s',
        handlers=[
            logging.FileHandler("input_error.log"),
            logging.StreamHandler()
        ]
    )
    
    def get_age():
        try:
            age = int(input("나이를 입력하세요: "))
            print(f"당신의 나이는 {age}세입니다.")
        except ValueError as e:
            logging.error(f"입력값 오류: {e}")
            print("숫자를 입력해주세요.")
    
    get_age()
    

    ✔️ 의사코드:

    사용자에게 숫자 입력 요청
    문자 등 잘못된 입력 시 ValueError 발생
    logging.error()로 오류 메시지 로그 파일과 콘솔에 기록
    

    🖨️ 출력결과: (콘솔)

    나이를 입력하세요: 스물
    숫자를 입력해주세요.
    [2025-05-21 12:30:10] ERROR: 입력값 오류: invalid literal for int() with base 10: '스물'
    

    🖨️ 출력결과:(input_error.log 파일)

    [2025-05-21 12:30:10] ERROR: 입력값 오류: invalid literal for int() with base 10: '스물'
    

    🔍 해설:

    • logging.error()는 예외 메시지만 간단하게 기록
    • 여러 개의 핸들러를 설정하여 파일 기록 + 콘솔 출력 동시 처리
    • 실무에서 입력 검증, API 응답 처리, 백엔드 로그 수집 등에 매우 많이 사용됨

    ✨ 실무 팁:

    • logging.exception()
      → 예외 발생 시 traceback까지 자동 포함해 기록
    • logging.error()
      → 간단한 에러 메시지 출력에 적합 (traceback은 포함 안 됨)
    • 로그 파일은 .log, .txt, 또는 중앙화된 로깅 시스템(예: ELK, Sentry 등)으로 관리
    • 여러 handlers를 통해 콘솔 + 파일 동시 기록 가능

    ◽ 사용자 정의 예외 Python에서는 Exception 클래스를 상속받아 직접 예외 클래스를 정의할 수 있습니다.
    이를 통해 도메인에 맞는 명확한 예외 표현, 로직 분기 처리, 에러 메시지 관리가 가능합니다.

    실무에서는 API 응답 오류, 데이터 누락, 검증 실패, 비즈니스 로직 
    오류 등을 구체적으로 분리할 때 자주 사용됩니다.
    

    </> 예시코드: ## 잘못된 할인율 예외 정의 및 처리

    class DiscountRateError(Exception):
        """할인율이 유효하지 않을 때 발생하는 예외"""
        pass
    
    def apply_discount(rate):
        if rate < 0 or rate > 100:
            raise DiscountRateError(f"잘못된 할인율: {rate}% (0~100 사이여야 함)")
        print(f"할인율 {rate}% 적용 완료")
    
    try:
        apply_discount(150)
    except DiscountRateError as e:
        print(e)
    

    ✔️ 의사코드:

    DiscountRateError라는 사용자 정의 예외 클래스 정의
    할인율이 0~100 사이가 아니면 해당 예외 발생
    예외 발생 시 사용자에게 메시지 출력
    

    🖨️ 출력결과:

    잘못된 할인율: 150% (0~100 사이여야 함)
    

    🔍 해설:

    • 비즈니스 로직 상에서 명확히 잘못된 상황을 정의하고 처리
    • 실무에서 "가격", "수량", "포인트" 등 숫자 유효성 검사에 자주 활용

    </> 예시코드: ## 설정 파일 누락 시 사용자 정의 예외 발생

    class ConfigLoadError(Exception):
        """설정 파일을 불러오지 못했을 때 발생하는 예외"""
        def __init__(self, message):
            super().__init__(f"[설정 오류] {message}")
    
    def load_config(path):
        try:
            with open(path, "r", encoding="utf-8") as f:
                return f.read()
        except FileNotFoundError:
            raise ConfigLoadError(f"파일이 존재하지 않습니다: {path}")
    
    try:
        load_config("missing_config.ini")
    except ConfigLoadError as e:
        print(e)
    

    ✔️ 의사코드:

    ConfigLoadError 사용자 정의 예외 정의
    파일이 없으면 FileNotFoundError → ConfigLoadError로 변환하여 발생
    상위에서 예외 잡아서 메시지 출력
    

    🖨️ 출력결과:

    [설정 오류] 파일이 존재하지 않습니다: missing_config.ini
    

    🔍 해설:

    • 하위 예외(FileNotFoundError)를 감싸서 의미 있는 예외로 변환
    • 실무에서 설정 오류, 인증 실패, 연결 오류 등을 구체적 의미로 정의하고 분기 처리할 때 유용

    ✨ 실무 팁:

    • 사용자 정의 예외는 class MyError(Exception):으로 생성
    • 메시지 커스터마이징 가능 (__init__() 오버라이딩)
    • 실무에서는 비즈니스 도메인 기반 예외 이름을 정의하는 것이 좋음 (예: OrderValidationError, LoginFailureError)
    • 예외 발생 후 raise CustomError(...)로 명확한 오류 신호 전달 가능

    ◽ 예외 핸들러 함수화 예외 처리 로직이 여러 곳에서 반복된다면, 공통 예외 처리 함수를 만들어 재사용하는 것이 좋습니다. 이렇게 하면 코드가 간결해지고 유지보수가 쉬워지며, 로깅, 사용자 `메시지, 알림 처리도 일관되게 관리할 수 있습니다.

    실무에서는 API 응답 검증, 사용자 입력 처리, 파일/DB 작업에서 자주 
    사용됩니다.
    

    </> 예시코드: 사용자 입력 예외 핸들러 함수화

    def safe_int_input(prompt):
        try:
            return int(input(prompt))
        except ValueError:
            print("❌ 숫자로 입력해주세요.")
            return None
    
    def get_quantity():
        qty = safe_int_input("수량을 입력하세요: ")
        if qty is not None:
            print(f"{qty}개가 입력되었습니다.")
    
    get_quantity()
    

    ✔️ 의사코드:

    입력값을 정수로 변환하는 공통 함수 정의
    ValueError 발생 시 안내 메시지 출력 후 None 반환
    사용 함수에서 결과 확인 후 후속 처리
    

    🖨️ 출력결과:

    숫자로 입력해주세요.
    

    🔍 해설:

    • safe_int_input() 함수로 예외 처리 로직을 캡슐화
    • 다른 입력 항목에서도 동일한 방식으로 재사용 가능

    </> 예시코드: 파일 열기 예외 핸들러 모듈화

    def safe_open(filepath, mode="r", encoding="utf-8"):
        try:
            return open(filepath, mode, encoding=encoding)
        except FileNotFoundError:
            print(f"파일을 찾을 수 없습니다: {filepath}")
        except PermissionError:
            print(f"파일 접근 권한이 없습니다: {filepath}")
        return None
    
    def read_settings():
        file = safe_open("settings.ini")
        if file:
            with file:
                print(file.read())
    
    read_settings()
    

    ✔️ 의사코드:

    파일 열기 시 발생할 수 있는 예외 처리 함수 정의
    FileNotFoundError 또는 PermissionError를 사용자 메시지로 안내
    정상 열기된 파일 객체만 반환하여 후속 처리
    

    🖨️ 출력결과:

    파일을 찾을 수 없습니다: settings.ini
    

    🔍 해설:

    • safe_open() 함수 하나로 파일 관련 예외를 통합 관리
    • 다양한 파일을 열 때 일관된 방식으로 사용할 수 있음
    • 실무에서 설정 파일, 로그 파일, CSV 로드 등에 자주 사용

    📝 문제1] int() 변환 중 오류가 발생하면 로그를 출력한 후, 예외를 다시 상위에 전달하세요.

    🖨️ 출력결과:

    [로그] 잘못된 입력: abc
    숫자로 변환할 수 없습니다.
    

    ✅ 정답:

    def parse_int(value):
        try:
            return int(value)
        except ValueError as e:
            print(f"[로그] 잘못된 입력: {value}")
            raise
    
    try:
        parse_int("abc")
    except ValueError:
        print("숫자로 변환할 수 없습니다.")
    

    🔍 해설: 하위 함수에서 raise로 예외를 재발생시켜, 상위 함수에서 최종 처리할 수 있도록 합니다.


    📝 문제2] 파일 열기 실패 시 FileNotFoundError를 사용자 정의 예외 ConfigLoadError로 변환하여 재발생시키세요.

    🖨️ 출력결과:

    [오류] 설정 파일이 존재하지 않습니다.
    

    ✅ 정답:

    class ConfigLoadError(Exception):
        pass
    
    def load_config():
        try:
            with open("missing.ini", "r") as f:
                return f.read()
        except FileNotFoundError:
            raise ConfigLoadError("설정 파일이 존재하지 않습니다.")
    
    try:
        load_config()
    except ConfigLoadError as e:
        print(f"[오류] {e}")
    

    🔍 해설: 하위 시스템 예외를 감싸서 도메인 예외로 변환 → 더 명확한 흐름 관리 가능.


    📝 문제3] logging을 이용해 ZeroDivisionError가 발생했을 때 에러 메시지를 로그 파일에 기록하세요.

    🖨️ 출력결과:

    계산 오류 발생
    

    ✅ 정답:

    import logging
    
    logging.basicConfig(filename="error.log", level=logging.ERROR)
    
    try:
        x = 10 / 0
    except ZeroDivisionError:
        logging.exception("계산 중 오류 발생")
        print("계산 오류 발생")
    

    🔍 해설: logging.exception()은 traceback 전체를 로그에 자동 저장하며, 콘솔에는 간단한 메시지만 출력합니다.


    📝 문제4] logging.error()로 사용자 입력 오류를 로그와 콘솔에 동시에 출력하세요.

    🖨️ 출력결과:

    유효하지 않은 입력입니다.
    

    ✅ 정답:

    import logging
    
    logging.basicConfig(
        level=logging.ERROR,
        format="%(message)s",
        handlers=[
            logging.FileHandler("input_error.log"),
            logging.StreamHandler()
        ]
    )
    
    try:
        age = int("스물다섯")
    except ValueError:
        logging.error("유효하지 않은 입력입니다.")
    

    🔍 해설: logginghandlers를 활용하면 파일 + 콘솔 로그를 동시에 출력할 수 있습니다.


    📝 문제5] 음수 잔액이 입력되면 사용자 정의 예외 NegativeBalanceError를 발생시키고, 이를 처리하세요.

    🖨️ 출력결과:

    잔액은 음수일 수 없습니다.
    

    ✅ 정답:

    class NegativeBalanceError(Exception):
        pass
    
    def update_balance(amount):
        if amount < 0:
            raise NegativeBalanceError("잔액은 음수일 수 없습니다.")
        print(f"잔액: {amount}원")
    
    try:
        update_balance(-100)
    except NegativeBalanceError as e:
        print(f"{e}")
    

    🔍 해설: 도메인 예외를 명확히 구분하여 상황을 잘 설명할 수 있도록 사용자 정의 예외를 설계합니다.


    📝 문제6] 18세 미만의 나이에 대해 사용자 정의 예외 UnderAgeError를 발생시키세요.

    🖨️ 출력결과:

    미성년자는 이용할 수 없습니다.
    

    ✅ 정답:

    class UnderAgeError(Exception):
        pass
    
    def check_age(age):
        if age < 18:
            raise UnderAgeError("미성년자는 이용할 수 없습니다.")
        print("✅ 이용 가능")
    
    try:
        check_age(15)
    except UnderAgeError as e:
        print(f"{e}")
    

    🔍 해설: 특정 비즈니스 조건을 위반하는 경우 사용자 정의 예외로 예외 흐름을 명확히 관리합니다.


    📝 문제7] 정수로 변환하는 기능을 safe_int() 함수로 분리하여 예외를 공통 처리하세요.

    🖨️ 출력결과:

    정수가 아닙니다.
    

    ✅ 정답:

    def safe_int(value):
        try:
            return int(value)
        except ValueError:
            print("정수가 아닙니다.")
            return None
    
    safe_int("abc")
    

    🔍 해설: 공통된 예외 로직을 함수로 분리하면 코드 재사용성유지보수성이 높아집니다.


    📝 문제8] 파일 열기 예외를 처리하는 공통 핸들러 safe_open()을 구현하고 이를 사용하세요.

    🖨️ 출력결과:

    파일 열기에 실패했습니다.
    

    ✅ 정답:

    def safe_open(filepath):
        try:
            return open(filepath, "r")
        except OSError:
            print("파일 열기에 실패했습니다.")
            return None
    
    f = safe_open("notfound.txt")
    

    🔍 해설: 파일 열기, DB 연결 등 반복되는 위험한 연산은 공통 함수로 분리하여 예외를 일관성 있게 처리합니다.


    🔹 강제로 예외 발생시키기

    raise 문 사용 raise 문을 사용하면 조건에 따라 의도적으로 예외를 발생시킬 수 있습니다. 이는 비즈니스 로직에서 잘못된 상황이 감지되었을 때, 정상 흐름을 차단하고 오류 처리로 분기하기 위해 사용됩니다.

    실무에서는 입력값 유효성 검사, API 응답 검증, 로직 위반 감지 등에 
    자주 사용됩니다.
    

    </> 예시코드: 입력 조건 위반 시 raise ValueError

    def set_discount(rate):
        if not (0 <= rate <= 100):
            raise ValueError(f"할인율은 0에서 100 사이여야 합니다: 입력값={rate}")
        print(f"할인율 {rate}%가 적용되었습니다.")
    
    try:
        set_discount(120)
    except ValueError as e:
        print(e)
    

    ✔️ 의사코드:

    할인율 입력값이 0~100 범위를 벗어나면 ValueError 강제로 발생
    정상인 경우 할인율 출력
    

    🖨️ 출력결과:

    할인율은 0에서 100 사이여야 합니다: 입력값=120
    

    🔍 해설:

    • 조건이 잘못되었음을 감지하고 raise로 예외를 발생시켜 흐름을 중단
    • 실무에서 수치 검증, 포인트 제한 등에서 유용하게 사용

    </> 예시코드: 사용자 인증 실패 시 사용자 정의 예외 raise

    class AuthenticationError(Exception):
        pass
    
    def login(username, password):
        if username != "admin" or password != "1234":
            raise AuthenticationError("인증 실패: 아이디 또는 비밀번호가 올바르지 않습니다.")
        print("로그인 성공!")
    
    try:
        login("admin", "wrongpass")
    except AuthenticationError as e:
        print(e)
    

    ✔️ 의사코드:

    아이디와 비밀번호가 틀리면 AuthenticationError 예외를 강제로 발생
    정상 입력 시 로그인 성공 출력
    

    🖨️ 출력결과:

    인증 실패: 아이디 또는 비밀번호가 올바르지 않습니다.
    

    🔍 해설:

    • 인증 로직에서 조건이 충족되지 않으면 예외를 의도적으로 발생
    • 사용자 정의 예외를 사용하여 예외의 의미를 명확히 전달
    • 실무에서 인증, 권한 검증, 유저 권한 제어 등에서 자주 사용됨

    ✨ 실무팁:

    • raise는 조건 위반 시 예외를 명확하게 드러내고 흐름을 중단할 때 사용
    • raise ValueError(...), raise CustomError(...)처럼 상황에 맞는 예외를 사용
    • raise 문으로 내부 로직을 강제적으로 분기하여 버그를 예방하거나 정책을 강제할 수 있음

    ◽ 사용자 정의 예외 발생 사용자 정의 예외(Custom Exception)는 Exception 클래스를 상속받아 직접 정의한 예외입니다.
    이 예외를 raise를 통해 직접 발생시킴으로써, 특정 비즈니스 조건이나 정책 위반 시 흐름을 제어할 수 있습니다.

    실무에서는 유효성 검사, 권한 제어, 데이터 무결성 체크 등의 상황에서 
    자주 사용되며,
    복잡한 로직을 명확하게 분리하고 처리 흐름을 컨트롤할 수 있게 
    해줍니다.
    

    </> 예시코드: 나이 제한 사용자 정의 예외 발생

    class AgeRestrictionError(Exception):
        pass
    
    def check_age(age):
        if age < 18:
            raise AgeRestrictionError("❌ 미성년자는 이 서비스에 접근할 수 없습니다.")
        print("✅ 서비스 이용 가능")
    
    try:
        check_age(16)
    except AgeRestrictionError as e:
        print(e)
    

    ✔️ 의사코드:

    AgeRestrictionError 예외 클래스 정의
    나이가 18 미만일 경우 예외를 강제로 발생
    try 블록에서 검사 후 except로 메시지 출력
    

    🖨️ 출력결과:

    미성년자는 이 서비스에 접근할 수 없습니다.
    

    🔍 해설:

    • 조건 위반 시 단순 메시지 출력이 아니라 예외 발생 → 상위 로직에서 분기 가능
    • 실무에서는 연령 제한, 등급 제한, 국가 제한 등 정책적 검증에 활용

    </> 예시코드: 잔액 부족 시 사용자 정의 예외 발생

    class InsufficientFundsError(Exception):
        def __init__(self, balance, amount):
            super().__init__(f"잔액 부족: 현재 잔액 {balance}원, 요청 금액 {amount}원")
    
    def withdraw(balance, amount):
        if amount > balance:
            raise InsufficientFundsError(balance, amount)
        print(f"{amount}원 출금 완료. 남은 잔액: {balance - amount}원")
    
    try:
        withdraw(5000, 10000)
    except InsufficientFundsError as e:
        print(e)
    

    ✔️ 의사코드:

    InsufficientFundsError 예외 클래스 정의
    잔액보다 출금액이 많으면 예외 발생
    잔액 정보 포함한 메시지로 안내
    

    🖨️ 출력결과:

    잔액 부족: 현재 잔액 5000원, 요청 금액 10000

    🔍 해설:

    • 예외 클래스에 추가 정보(balance, amount)를 전달하여 상황을 더 명확하게 설명
    • 실무에서는 결제 시스템, 예치금 시스템, 포인트 차감 등 금전 처리 로직에 필수

    ◽ 유효성 검증용 예외 입력값이나 조건이 올바르지 않을 경우, raise ValueError("...")를 사용하여 의도적으로 예외를 발생시키는 방식입니다. 이 방식은 입력 유효성 검사, 비즈니스 로직 조건 검사, API 파라미터 검증 등에 널리 사용됩니다.

    실무에서는 ValueError, TypeError, 사용자 정의 예외 등을 사용하여 
    비정상 데이터 흐름을 명확히 차단합니다.
    

    </> 예시코드: 비밀번호 길이 검증 후 ValueError 발생

    def validate_password(password):
        if len(password) < 8:
            raise ValueError("❌ 비밀번호는 최소 8자 이상이어야 합니다.")
        print("✅ 비밀번호 형식이 유효합니다.")
    
    try:
        validate_password("abc123")
    except ValueError as e:
        print(e)
    

    ✔️ 의사코드:

    비밀번호 길이가 8자 미만이면 ValueError 예외 발생
    정상일 경우 "유효" 메시지 출력
    

    🖨️ 출력결과:

    비밀번호는 최소 8자 이상이어야 합니다.
    

    🔍 해설:

    • 유효하지 않은 입력을 raise로 강제 차단
    • 사용자에게 명확한 메시지를 제공하여 UI/UX 향상
    • 실무에서는 사용자 가입, 정보 수정, 인증 등에서 빈번하게 사용

    </> 예시코드: 이메일 형식 검증 (정규표현식 + ValueError)

    import re
    
    def validate_email(email):
        pattern = r"^[\w\.-]+@[\w\.-]+\.\w+$"
        if not re.match(pattern, email):
            raise ValueError("❌ 이메일 형식이 올바르지 않습니다.")
        print("✅ 이메일 형식이 유효합니다.")
    
    try:
        validate_email("not-an-email")
    except ValueError as e:
        print(e)
    

    ✔️ 의사코드:

    정규표현식으로 이메일 형식 검사
    형식이 맞지 않으면 ValueError 예외 발생
    정상일 경우 "유효" 메시지 출력
    

    🖨️ 출력결과:

    이메일 형식이 올바르지 않습니다.
    

    🔍 해설:

    • 정규식을 이용한 형식 검사 → 실패 시 예외 발생
    • API, DB 저장, 사용자 등록 등 모든 입력 유효성 검사에 매우 중요
    • 코드 흐름 상 잘못된 데이터는 즉시 차단해야 함

    ✨ 실무 팁:

    • raise ValueError("메시지")는 간단한 유효성 검증에 가장 많이 사용됨
    • if not condition: 패턴과 함께 예외를 일관되게 사용하면 유지보수에 유리
    • 필요한 경우 raise CustomValidationError(...) 형태로 도메인 특화 예외로 확장 가능

    ◽ 테스트를 위한 예외 발생 Python의 unittest 모듈에서는 특정 코드가 예외를 발생시키는지를 검증하기 위해 assertRaises 메서드를 사용합니다.

    이 방식은 유효성 검사, 경계값 처리, 실패 조건 확인 등
    실무 테스트 자동화에서 예외 발생 여부를 보장하는 데 자주 사용됩니다.
    

    </> 예시코드: ValueError 발생 확인 테스트

    import unittest
    
    def parse_age(age_str):
        if not age_str.isdigit():
            raise ValueError("나이는 숫자여야 합니다.")
        return int(age_str)
    
    class TestAgeParsing(unittest.TestCase):
        def test_invalid_age(self):
            with self.assertRaises(ValueError):
                parse_age("스무살")
    
    if __name__ == "__main__":
        unittest.main()
    

    ✔️ 의사코드:

    문자열이 숫자가 아니면 ValueError 발생하는 함수 정의
    테스트 케이스에서 assertRaises로 예외 발생 확인
    

    🖨️ 출력결과:

    .
    ----------------------------------------------------------------------
    Ran 1 test in 0.000s
    
    OK
    

    🔍 해설:

    • "스무살"은 숫자가 아니므로 ValueError 발생
    • assertRaises(ValueError)를 통해 예외가 제대로 발생하는지 검증
    • 실무에서 입력 유효성 검증 로직을 테스트할 때 매우 중요

    </> 예시코드: 사용자 정의 예외 테스트

    import unittest
    
    class AuthenticationError(Exception):
        pass
    
    def login(user, password):
        if password != "1234":
            raise AuthenticationError("비밀번호가 일치하지 않습니다.")
        return True
    
    class TestLogin(unittest.TestCase):
        def test_authentication_failure(self):
            with self.assertRaises(AuthenticationError):
                login("admin", "wrongpass")
    
    if __name__ == "__main__":
        unittest.main()
    

    ✔️ 의사코드:

    비밀번호가 틀리면 AuthenticationError 발생
    테스트에서 assertRaises로 해당 예외 확인
    

    🖨️ 출력결과:

    .
    ----------------------------------------------------------------------
    Ran 1 test in 0.000s
    
    OK
    

    🔍 해설:

    • 사용자 정의 예외도 assertRaises로 검증 가능
    • 인증 실패, 접근 권한 오류 등 로직 기반 예외의 정확한 동작 여부를 테스트할 수 있음

    ✨ 실무 팁:

    • assertRaises는 예외 발생이 의도된 코드의 신뢰성을 보장하는 데 사용
    • with self.assertRaises(...) 구문은 가독성 좋고 권장되는 방식
    • 사용자 정의 예외도 동일한 방식으로 테스트 가능
    • 예외 발생 전후의 경계값, 조건 분기 테스트에 효과적

    📝 문제1] 정수가 0보다 작을 경우, raise 문으로 예외를 강제로 발생시키세요.

    🖨️ 출력결과:

    0 이상만 허용됩니다.
    

    ✅ 정답:

    num = -3
    
    if num < 0:
        raise ValueError("❌ 0 이상만 허용됩니다.")
    

    🔍 해설: raise 문을 조건에 따라 직접 호출하면 예외를 명시적으로 발생시킬 수 있습니다.


    📝 문제2] None이 입력될 경우 TypeErrorraise를 이용해 발생시키세요.

    🖨️ 출력결과:

    TypeError: 입력값이 None입니다.
    

    ✅ 정답:

    def process(data):
        if data is None:
            raise TypeError("입력값이 None입니다.")
        print("처리 완료")
    
    process(None)
    

    🔍 해설: 함수 내부 조건을 만족하지 않을 경우 예외를 발생시켜 명확한 오류 흐름을 제공합니다.


    📝 문제3] 사용자 정의 예외 InvalidAgeError를 정의하고, 나이가 0 미만이면 예외를 발생시키세요.

    🖨️ 출력결과:

    유효하지 않은 나이입니다.
    

    ✅ 정답:

    class InvalidAgeError(Exception):
        pass
    
    def check_age(age):
        if age < 0:
            raise InvalidAgeError("유효하지 않은 나이입니다.")
        print("정상 입력")
    
    try:
        check_age(-5)
    except InvalidAgeError as e:
        print(e)
    

    🔍 해설: 사용자 정의 예외로 의미 있는 오류 이름을 붙이면, 예외 흐름이 더 읽기 쉽고 명확해집니다.


    📝 문제4] PermissionDeniedError 예외를 만들어, 권한이 없을 경우 이를 발생시키는 코드를 작성하세요.

    🖨️ 출력결과:

    접근 권한이 없습니다.
    

    ✅ 정답:

    class PermissionDeniedError(Exception):
        pass
    
    def access_page(user_role):
        if user_role != "admin":
            raise PermissionDeniedError("❌ 접근 권한이 없습니다.")
        print("관리자 페이지 접근 허용")
    
    try:
        access_page("guest")
    except PermissionDeniedError as e:
        print(e)
    

    🔍 해설: 권한 검사 로직에 예외를 결합하면 보안성과 유지보수성이 향상됩니다.


    📝 문제5] 입력된 문자열이 비어 있으면 raise ValueError(...)를 이용해 유효성 오류를 발생시키세요.

    🖨️ 출력결과:

    빈 문자열은 입력할 수 없습니다.
    

    ✅ 정답:

    text = ""
    
    if not text:
        raise ValueError("❌ 빈 문자열은 입력할 수 없습니다.")
    

    🔍 해설: 입력 유효성을 검사하는 가장 기본적인 예외 발생 예시입니다.


    📝 문제6] 입력된 이메일이 "@"를 포함하지 않으면 예외를 발생시키세요.

    🖨️ 출력결과:

    이메일 형식이 올바르지 않습니다.
    

    ✅ 정답:

    def validate_email(email):
        if "@" not in email:
            raise ValueError("❌ 이메일 형식이 올바르지 않습니다.")
        print("이메일 유효성 통과")
    
    validate_email("hello.com")
    

    🔍 해설: 검증 로직에서 직접 raise를 사용하여 데이터 품질을 보장합니다.


    📝 문제7] unittest를 사용하여 특정 함수가 ValueError를 발생시키는지 테스트하세요.

    🖨️ 출력결과:

    .
    ----------------------------------------------------------------------
    Ran 1 test in 0.000s
    
    OK
    

    ✅ 정답:

    import unittest
    
    def to_int(value):
        return int(value)
    
    class TestConversion(unittest.TestCase):
        def test_invalid_int(self):
            with self.assertRaises(ValueError):
                to_int("문자")
    
    if __name__ == "__main__":
        unittest.main()
    

    🔍 해설: assertRaises()를 사용하면 테스트 중 예외 발생 유무를 검증할 수 있습니다.


    📝 문제8] PermissionError가 발생하는 상황을 테스트하는 단위 테스트 코드를 작성하세요.

    🖨️ 출력결과:

    .
    ----------------------------------------------------------------------
    Ran 1 test in 0.000s
    
    OK
    

    ✅ 정답:

    import unittest
    
    def restricted_action(role):
        if role != "admin":
            raise PermissionError("접근 거부")
    
    class TestAuth(unittest.TestCase):
        def test_permission_denied(self):
            with self.assertRaises(PermissionError):
                restricted_action("guest")
    
    if __name__ == "__main__":
        unittest.main()
    

    🔍 해설: 예외 발생이 정상 동작인 경우 테스트로 검증하여 로직 안정성 확보에 기여합니다.



    💭직접 풀어보세요.

    📝 문제1] 사용자에게 정수를 입력받고, ValueError가 발생할 경우 "숫자를 입력해주세요"를 출력하세요. 입력에 성공하면 "입력된 숫자: ___" 형태로 출력합니다.

    🖨️ 출력결과: 입력값: abc

    숫자를 입력해주세요
    

    🖨️ 출력결과: 입력값: 10

    입력된 숫자: 10
    

    ✅ 정답:

    try:
        num = int(input("숫자를 입력하세요: "))
        print(f"입력된 숫자: {num}")
    except ValueError:
        print("숫자를 입력해주세요")
    

    🔍 해설: int() 함수는 숫자가 아닌 문자열을 변환하려 할 때 ValueError를 발생시킵니다. try-except로 처리하여 프로그램이 종료되지 않도록 합니다.


    📝 문제2] 문자열을 int()로 변환하고, 그 값을 리스트 인덱스로 접근하려 합니다. 입력이 "문자"거나 "10"처럼 인덱스를 벗어나면 예외를 잡아 "입력 오류" 또는 "범위 초과"를 출력하세요.

    🖨️ 출력결과: 입력값: 문자

    입력 오류
    

    🖨️ 출력결과: 입력값: 10

    범위 초과
    

    ✅ 정답:

    nums = [5, 10, 15]
    
    try:
        idx = int(input("인덱스를 입력하세요: "))
        print(f"값: {nums[idx]}")
    except ValueError:
        print("입력 오류")
    except IndexError:
        print("범위 초과")
    

    🔍 해설: 두 가지 예외(ValueError, IndexError)를 구분하여 적절한 메시지로 분기 처리합니다.


    📝 문제3] 나이가 음수일 경우 InvalidAgeError 사용자 정의 예외를 발생시키고, 내부에서 ValueError로 감싸서 상위에 재전달하세요.

    🖨️ 출력결과:

    [로그] 음수 나이 입력됨
    잘못된 나이 형식입니다.
    

    ✅ 정답:

    class InvalidAgeError(Exception):
        pass
    
    def check_age(age):
        try:
            if age < 0:
                raise InvalidAgeError("음수 나이 입력됨")
        except InvalidAgeError as e:
            print(f"[로그] {e}")
            raise ValueError("잘못된 나이 형식입니다.") from e
    
    try:
        check_age(-5)
    except ValueError as e:
        print(f"{e}")
    
    

    🔍 해설: 예외를 한 번 처리한 후 다시 발생시키는 예외 재발생 (raise ... from) 패턴을 보여줍니다. 도메인 예외 → 표준 예외로 변환.


    📝 문제4] 입력값이 실수로 변환 가능한지 확인하고, 불가능하면 "변환 실패" 메시지를 출력하세요.

    🖨️ 출력결과: 입력값: abc

    변환 실패
    

    ✅ 정답:

    try:
        value = float(input("실수를 입력하세요: "))
        print(f"입력된 값: {value}")
    except ValueError:
        print("변환 실패")
    

    🔍 해설: float() 변환 과정에서 잘못된 형식이 들어오면 ValueError가 발생합니다.


    📝 문제5] 10 / 0 연산 시 ZeroDivisionError가 발생하면 콘솔과 파일 log.txt에 에러 메시지를 기록하세요.

    🖨️ 출력결과: 콘솔

    연산 실패 (로그에 기록됨)
    

    🖨️ 출력결과: log.txt 내용

    ERROR:root:ZeroDivisionError 발생
    Traceback (most recent call last):
    ...
    ZeroDivisionError: division by zero
    

    ✅ 정답:

    import logging
    
    logging.basicConfig(filename="log.txt", level=logging.ERROR)
    
    try:
        result = 10 / 0
    except ZeroDivisionError:
        logging.exception("ZeroDivisionError 발생")
        print("연산 실패 (로그에 기록됨)")
    

    🔍 해설: logging.exception()은 traceback 전체를 포함한 메시지를 자동 기록합니다. logging은 실무에서 오류 추적과 분석에 필수입니다.


    📝 문제6] 파일을 열고 내용을 출력하는 safe_open() 함수를 정의하세요.
    파일이 없으면 "파일을 찾을 수 없습니다." 메시지를 출력하고, 예외 없이 흐름을 유지하세요.

    🖨️ 출력결과:

    파일을 찾을 수 없습니다.
    

    ✅ 정답:

    def safe_open(path):
        try:
            with open(path, "r") as f:
                print(f.read())
        except FileNotFoundError:
            print("파일을 찾을 수 없습니다.")
    
    safe_open("unknown.txt")
    

    🔍 해설: 반복되는 파일 예외 처리를 함수로 캡슐화하면 재사용성 증가와 에러 흐름 통제가 쉬워집니다.


    📝 문제7] 사용자로부터 정수를 입력받아, 성공하면 "입력 완료"를 출력하세요. 숫자가 아닌 값을 입력하면 "숫자를 입력해주세요"를 출력하고, 정상 입력 시에는 else 절을 활용하세요.

    🖨️ 출력결과: 입력: abc

    숫자를 입력해주세요
    

    ✅ 정답:

    try:
        age = int(input("나이를 입력하세요: "))
    except ValueError:
        print("숫자를 입력해주세요")
    else:
        print(f"입력 완료: {age}")
    

    🔍 해설: try-except-else 구조는 예외가 발생하지 않았을 때만 else가 실행되어 정상 흐름과 예외 흐름을 구분해줍니다.


    📝 문제8] 사용자에게 인덱스를 입력받아 리스트 값을 출력하려 합니다.
    문자 입력 또는 인덱스 초과 시 각각 다른 예외 메시지를 출력하세요.

    🖨️ 출력결과: 입력: 문자

    숫자를 입력하세요.
    

    🖨️ 출력결과: 입력: 5

    인덱스를 벗어났습니다.
    

    ✅ 정답:

    items = ["사과", "바나나", "포도"]
    
    try:
        idx = int(input("인덱스를 입력하세요: "))
        print(items[idx])
    except ValueError:
        print("숫자를 입력하세요.")
    except IndexError:
        print("인덱스를 벗어났습니다.")
    

    🔍 해설: ValueErrorIndexError는 예외 원인이 다르므로 구분하여 사용자에게 정확한 안내를 제공합니다.


    📝 문제9] 0 이하의 값을 입력하면 사용자 정의 예외 InvalidNumberError를 발생시키고 이를 처리하세요.

    🖨️ 출력결과:

    0보다 큰 수를 입력하세요.
    

    ✅ 정답:

    class InvalidNumberError(Exception):
        pass
    
    def validate_number(n):
        if n <= 0:
            raise InvalidNumberError("0보다 큰 수를 입력하세요.")
    
    try:
        validate_number(-1)
    except InvalidNumberError as e:
        print(f"{e}")
    

    🔍 해설: 비즈니스 로직에서 의도적인 오류 상황에 대해 도메인 예외를 정의하여 코드 명확성과 유지보수성을 높입니다.


    📝 문제10] 0으로 나누기를 시도할 경우 logging 모듈을 사용하여 error.log에 예외를 기록하고, 콘솔에는 "오류 발생"을 출력하세요.

    🖨️ 출력결과: 콘솔

    오류 발생
    

    🖨️ 출력결과: error.log

    ERROR:root:예외 발생
    Traceback (most recent call last):
    ...
    ZeroDivisionError: division by zero
    

    ✅ 정답:

    import logging
    
    logging.basicConfig(filename="error.log", level=logging.ERROR)
    
    try:
        x = 10 / 0
    except ZeroDivisionError:
        logging.exception("예외 발생")
        print("오류 발생")
    

    🔍 해설: logging.exception()은 예외 메시지와 함께 traceback을 자동으로 기록하여 디버깅에 유리합니다.


    📝 문제11] 딕셔너리 리스트 내에서 "email" 키가 없는 경우와 리스트 구조가 잘못된 경우를 모두 예외 처리하세요.

    🖨️ 출력결과:

    홍길동 - hong@test.com
    ❌ [2] 형식 오류
    ❌ [3] 키 누락: 'email'
    

    ✅ 정답:

    users = [
        {"name": "홍길동", "email": "hong@test.com"},
        "문자열",
        {"name": "김영희"}
    ]
    
    for i, user in enumerate(users, start=1):
        try:
            print(f"{user['name']} - {user['email']}")
        except KeyError as e:
            print(f"[{i}] 키 누락: {e}")
        except TypeError:
            print(f"[{i}] 형식 오류")
    

    🔍 해설: 중첩된 데이터 구조에서는 키 누락(KeyError)과 타입 불일치(TypeError)를 동시에 방어하는 것이 필수입니다.


    📝 문제12] divide(x, y) 함수가 0으로 나누면 ZeroDivisionError를 발생시키는지 unittest로 테스트하세요.

    🖨️ 출력결과:

    .
    ----------------------------------------------------------------------
    Ran 1 test in 0.000s
    
    OK
    

    ✅ 정답:

    import unittest
    
    def divide(x, y):
        return x / y
    
    class TestDivide(unittest.TestCase):
        def test_zero_division(self):
            with self.assertRaises(ZeroDivisionError):
                divide(10, 0)
    
    if __name__ == "__main__":
        unittest.main()
    

    🔍 해설: assertRaises()특정 상황에서 예외가 발생하는지 검증하는 데 사용되며, 테스트 자동화에 매우 중요합니다.


    📝 문제13] 사용자에게 나이를 입력받고 정수로 변환하세요.
    숫자가 아닌 입력이 들어오면 "나이는 숫자로 입력해주세요"를 출력하세요.

    🖨️ 출력결과: 입력: 열아홉

    나이는 숫자로 입력해주세요
    

    ✅ 정답:

    try:
        age = int(input("나이를 입력하세요: "))
        print(f"입력된 나이: {age}")
    except ValueError:
        print("나이는 숫자로 입력해주세요")
    

    🔍 해설: int()는 숫자가 아닌 문자열을 변환할 경우 ValueError를 발생시키므로 except 블록에서 잡아야 합니다.


    📝 문제14] 존재하지 않는 파일을 읽으려다 발생하는 예외를 OSError로 처리하여 "파일 열기 실패"를 출력하세요.

    🖨️ 출력결과:

    파일 열기 실패
    

    ✅ 정답:

    try:
        with open("missing.txt", "r") as f:
            content = f.read()
    except OSError:
        print("파일 열기 실패")
    

    🔍 해설: OSErrorFileNotFoundError, PermissionError 등 다양한 I/O 관련 예외를 포괄합니다.


    📝 문제15] 0 이하의 금액을 입력하면 InvalidAmountError 예외를 발생시키고 이를 잡아 "유효하지 않은 금액입니다."라고 출력하세요.

    🖨️ 출력결과:

    유효하지 않은 금액입니다.
    

    ✅ 정답:

    class InvalidAmountError(Exception):
        pass
    
    def set_price(price):
        if price <= 0:
            raise InvalidAmountError("유효하지 않은 금액입니다.")
    
    try:
        set_price(0)
    except InvalidAmountError as e:
        print(f"{e}")
    

    🔍 해설: 사용자 정의 예외는 도메인 특화된 예외 상황을 명확하게 표현할 수 있습니다. raise로 명시적 오류 발생.


    📝 문제16] 0으로 나누는 연산에서 발생한 오류를 logging을 통해 파일 error.log에 저장하고, 콘솔에는 "계산 실패"를 출력하세요.

    🖨️ 출력결과: 콘솔

    계산 실패
    

    ✅ 정답:

    import logging
    
    logging.basicConfig(filename="error.log", level=logging.ERROR)
    
    try:
        result = 10 / 0
    except ZeroDivisionError:
        logging.exception("0으로 나누기 오류 발생")
        print("계산 실패")
    

    🔍 해설: logging.exception()은 예외 메시지와 traceback을 함께 기록하여 디버깅 및 감사 로그로 활용됩니다.


    📝 문제17] 사용자에게 정수를 입력받아 올바른 입력이 들어올 때까지 반복하세요. 잘못된 입력에는 "숫자만 입력 가능"을 출력하세요.

    🖨️ 출력결과:

    입력하세요: 스물  
    숫자만 입력 가능  
    입력하세요: 25  
    입력값: 25
    

    ✅ 정답:

    while True:
        try:
            num = int(input("입력하세요: "))
            print(f"입력값: {num}")
            break
        except ValueError:
            print("숫자만 입력 가능")
    

    🔍 해설: while True + try-except는 입력 유효성을 반복해서 확인하는 가장 기본적이면서 효과적인 패턴입니다.


    📝 문제18] 리스트 안의 딕셔너리 구조에서 "email" 키가 없는 경우 KeyError, 딕셔너리가 아닌 항목에는 TypeError를 처리하세요.

    🖨️ 출력결과:

    홍길동 - hong@test.com  
    [2] 형식 오류  
    [3] email 정보 없음
    

    ✅ 정답:

    data = [
        {"name": "홍길동", "email": "hong@test.com"},
        "문자열",
        {"name": "김영희"}
    ]
    
    for i, item in enumerate(data, start=1):
        try:
            print(f"{item['name']} - {item['email']}")
        except KeyError:
            print(f"[{i}] email 정보 없음")
        except TypeError:
            print(f"[{i}] 형식 오류")
    
    

    🔍 해설: 복합 자료 구조에서는 KeyErrorTypeError가 동시에 발생할 수 있으므로 다중 예외 처리가 중요합니다.


    📝 문제19] 입력값을 float()으로 변환하고, 변환된 값이 0 이하이면 오류 메시지를 출력하세요.
    입력값이 숫자가 아니거나 None, 리스트 등이 들어올 경우 "변환 실패"를 출력하세요.

    🖨️ 출력결과:

    변환 실패
    

    ✅ 정답:

    def process_input(value):
        try:
            number = float(value)
            if number <= 0:
                raise ValueError("0 이하의 수는 허용되지 않습니다.")
            print(f"유효한 값: {number}")
        except (ValueError, TypeError):
            print("변환 실패")
    
    process_input("문자")
    process_input(-3)
    

    🔍 해설:

    • float() 변환 중 ValueError, TypeError 발생 가능
    • 변환 이후 값 조건도 체크하여 명시적 예외 발생 가능
    • 실무에서 자주 쓰이는 형변환 + 검증 + 예외 처리 패턴입니다.

    📝 문제20] 아래 함수를 테스트하는 코드를 작성하세요. calculate_ratio(x, y)y가 0이면 ZeroDivisionError를 발생시킵니다.
    단위 테스트에서 해당 예외 발생 여부를 확인하세요.

    🖨️ 출력결과:

    .
    ----------------------------------------------------------------------
    Ran 1 test in 0.000s
    
    OK
    

    ✅ 정답:

    import unittest
    
    def calculate_ratio(x, y):
        return x / y
    
    class TestRatio(unittest.TestCase):
        def test_divide_by_zero(self):
            with self.assertRaises(ZeroDivisionError):
                calculate_ratio(100, 0)
    
    if __name__ == "__main__":
        unittest.main()
    
    

    🔍 해설:

    • assertRaises는 특정 예외가 발생하는지 검증할 수 있는 unittest 핵심 메서드입니다.
    • 예외 발생이 "정상 동작"인 경우에도 테스트 대상이 되며, 방어 로직 테스트에 적합합니다.
    TOP
    preload preload