Python Programming
  • Home
  • Intro
    • History & Background
    • Python Setup
  • QPB
    • Part I: Chapter 1-3
    • Part II
    • 5. Lists, Tuples, Sets
  • Exercises
    • Chapter 5: Lists, Tuples, Sets
    • Chapter 6: Strings
    • Chapter 7: Dictionaries
    • Chapter 8: Control flow
    • Chapter 9: Functions
    • Chapter 14: Exceptions
    • Chapter 15: Classes
  • Exploring Data
    • NumPy & pandas
    • Visualization
  • Library System
  • Netflix Movie Analysis
    • Notes
    • Project-Native
    • Project-pandas
  • References
    • QPB Part 1
    • QPB Part 2
    • QPB Part 3
    • QPB Part 4

On this page

  • 문제 1: 기본 예외 처리
  • 문제 2: 여러 예외 타입 처리
  • 문제 3: 파일 처리와 예외
  • 문제 4: 사용자 정의 예외
  • 문제 5: assert 문 활용
  • 문제 6: 예외 재발생(Re-raising)
  • 문제 7: 예외 계층 구조 활용
  • 문제 8: EAFP vs LBYL
  • 문제 9: 종합 - 사용자 입력 검증 시스템
  • 문제 10**: 커스텀 Context Manager 만들기

Exercises: Chapter 14

Chapter 14: Exceptions - 연습문제

문제 1: 기본 예외 처리

사용자로부터 두 개의 숫자를 입력받아 나눗셈을 수행하는 함수를 작성하세요.

요구사항:

  1. 0으로 나누기 시도 시 적절한 메시지 출력
  2. 숫자가 아닌 값 입력 시 적절한 메시지 출력
  3. 정상적으로 실행되면 결과 반환
  4. 모든 경우에 “연산 시도 완료” 메시지 출력

힌트: try, except, else, finally 블록을 모두 활용하세요.

# 테스트
safe_divide(10, 2)    # 5.0
safe_divide(10, 0)    # "0으로 나눌 수 없습니다"
safe_divide(10, 'a')  # "숫자만 입력 가능합니다"
def safe_divide(a, b):
    """
    안전한 나눗셈 함수
    
    Args:
        a: 피제수
        b: 제수
    
    Returns:
        나눗셈 결과 또는 None (오류 시)
    """
    # 여기에 코드를 작성하세요
    pass


# 테스트

문제 2: 여러 예외 타입 처리

리스트에서 특정 인덱스의 요소를 가져오는 함수를 작성하되, 발생 가능한 여러 예외를 처리하세요.

처리해야 할 예외:

  1. IndexError: 인덱스가 범위를 벗어난 경우
  2. TypeError: 첫 번째 인자가 리스트가 아니거나, 두 번째 인자가 정수가 아닌 경우
  3. ValueError: 음수 인덱스를 허용하지 않는 경우 (직접 raise)

힌트: 여러 개의 except 블록을 사용하세요.

def get_element(data, index, allow_negative=True):
    """
    리스트에서 안전하게 요소를 가져오는 함수
    
    Args:
        data: 리스트
        index: 인덱스
        allow_negative: 음수 인덱스 허용 여부
    
    Returns:
        요소 또는 None (오류 시)
    """
    # 여기에 코드를 작성하세요
    pass


# 테스트
test_list = [10, 20, 30, 40, 50]

문제 3: 파일 처리와 예외

파일을 안전하게 읽어오는 함수를 작성하세요.

요구사항:

  1. 파일이 없으면 “파일을 찾을 수 없습니다” 메시지와 함께 빈 문자열 반환
  2. 권한이 없으면 “파일 읽기 권한이 없습니다” 메시지와 함께 빈 문자열 반환
  3. 인코딩 오류 발생 시 다른 인코딩(cp949)으로 재시도
  4. 그 외 I/O 오류는 일반 오류 메시지 출력

힌트: FileNotFoundError, PermissionError, UnicodeDecodeError, IOError를 처리하세요.

def safe_read_file(filename):
    """
    파일을 안전하게 읽어오는 함수
    
    Args:
        filename: 파일 경로
    
    Returns:
        파일 내용 또는 빈 문자열 (오류 시)
    """
    # 여기에 코드를 작성하세요
    pass


# 테스트

문제 4: 사용자 정의 예외

재고 관리 시스템을 구현하되, 사용자 정의 예외를 만들어 사용하세요.

요구사항:

  1. InvalidProductError: 존재하지 않는 상품을 주문할 때
  2. InvalidQuantityError: 수량이 0 이하일 때
  3. OutOfStockError: 재고가 부족할 때

힌트: 각 예외 클래스는 Exception을 상속받으며, 주문 처리 함수는 세 가지 검증(상품 존재 여부 → 수량 검증 → 재고 확인)을 순차적으로 수행해야 합니다.

# 사용자 정의 예외 클래스들
class InvalidProductError(Exception):
    """존재하지 않는 상품 예외"""
    pass

class InvalidQuantityError(Exception):
    """잘못된 주문 수량 예외"""
    pass

class OutOfStockError(Exception):
    """재고 부족 예외"""
    pass


def create_inventory():
    """기본 재고 생성"""
    return {
        "monitor": 5,
        "keyboard": 8,
        "mouse": 12,
    }


def validate_product(inventory, product_name):
    """상품 존재 여부 확인"""
    # 여기에 코드를 작성하세요
    pass


def validate_quantity(quantity):
    """수량 값 검증"""
    # 여기에 코드를 작성하세요
    pass


def reserve_stock(inventory, product_name, quantity):
    """재고에서 상품 예약"""
    # 1. 상품 존재 여부 확인
    # 2. 수량 검증
    # 3. 재고 부족 여부 확인
    # 4. 재고 차감 후 남은 재고 반환
    pass


# 테스트
def demo_order():
    inventory = create_inventory()
    try:
        reserve_stock(inventory, "monitor", 3)
        reserve_stock(inventory, "mouse", 20)
    except InvalidProductError as e:
        print(f"상품 오류: {e}")
    except InvalidQuantityError as e:
        print(f"수량 오류: {e}")
    except OutOfStockError as e:
        print(f"재고 부족: {e}")

# demo_order()

문제 5: assert 문 활용

데이터 검증 함수를 작성하되, assert 문을 사용하여 개발 중 버그를 빠르게 발견할 수 있도록 하세요.

요구사항:

  1. 학생 데이터의 타입과 값을 검증
  2. 이름은 비어있지 않은 문자열
  3. 나이는 0~150 사이의 정수
  4. 점수는 0~100 사이의 숫자 리스트
  5. 점수 리스트는 비어있지 않음

힌트: assert 문은 조건이 False일 때 AssertionError를 발생시킵니다.

def validate_student_data(name, age, scores):
    """
    학생 데이터 검증 (개발 중 디버깅용)
    
    Args:
        name: 학생 이름 (str)
        age: 나이 (int)
        scores: 점수 리스트 (list of int/float)
    
    Returns:
        평균 점수
    """
    # assert 문을 사용하여 검증하세요
    
    # 평균 점수 계산 및 반환
    pass


# 테스트

문제 6: 예외 재발생(Re-raising)

데이터베이스 연결을 시뮬레이션하는 함수를 작성하되, 예외를 잡아서 로깅한 후 다시 발생시키세요.

요구사항:

  1. 연결 시도 시 로그 메시지 출력
  2. 예외 발생 시 오류 로그 출력
  3. 예외를 다시 상위로 전파 (re-raise)
  4. 연결 종료 시도는 항상 수행

힌트: except 블록에서 raise만 사용하면 현재 예외를 다시 발생시킵니다.

def connect_to_database(connection_string):
    """
    데이터베이스 연결 시뮬레이션
    
    Args:
        connection_string: 연결 문자열
    
    Raises:
        ValueError: 유효하지 않은 연결 문자열
        ConnectionError: 연결 실패
    """
    print(f"[LOG] 데이터베이스 연결 시도: {connection_string}")
    
    try:
        # 연결 문자열 검증
        if not connection_string or connection_string == "invalid":
            raise ValueError("유효하지 않은 연결 문자열")
        
        if connection_string == "failed":
            raise ConnectionError("서버에 연결할 수 없습니다")
        
        print(f"[LOG] 연결 성공")
        
    except (ValueError, ConnectionError) as e:
        # 여기에 로깅 후 재발생 코드 작성
        pass
    
    finally:
        # 항상 실행되는 정리 코드
        pass


# 테스트

문제 7: 예외 계층 구조 활용

다양한 산술 예외를 처리하되, 예외의 계층 구조를 활용하여 효율적으로 처리하세요.

예외 계층:

ArithmeticError
    ├── ZeroDivisionError
    ├── OverflowError
    └── FloatingPointError

요구사항:

  1. ZeroDivisionError는 개별적으로 처리
  2. 나머지 ArithmeticError는 한 번에 처리
  3. 각 경우에 적절한 메시지 출력

힌트: 구체적인 예외를 먼저, 일반적인 예외를 나중에 배치하세요.

def safe_calculate(operation, a, b):
    """
    안전한 계산 함수
    
    Args:
        operation: 연산 종류 ('add', 'subtract', 'multiply', 'divide', 'power')
        a: 첫 번째 숫자
        b: 두 번째 숫자
    
    Returns:
        계산 결과 또는 None (오류 시)
    """
    try:
        if operation == 'add':
            return a + b
        elif operation == 'subtract':
            return a - b
        elif operation == 'multiply':
            return a * b
        elif operation == 'divide':
            return a / b
        elif operation == 'power':
            return a ** b
        else:
            raise ValueError(f"알 수 없는 연산: {operation}")
    
    # 여기에 예외 처리 코드 작성
    # 힌트: ZeroDivisionError를 먼저, 그 다음 ArithmeticError를 처리
    except ZeroDivisionError:
        pass  # 여기에 코드 작성
    except ArithmeticError:
        pass  # 여기에 코드 작성


# 테스트

문제 8: EAFP vs LBYL

같은 기능을 LBYL(Look Before You Leap)과 EAFP(Easier to Ask Forgiveness than Permission) 두 가지 방식으로 구현하고 차이를 비교하세요.

기능: 딕셔너리에서 키의 값을 가져오되, 없으면 기본값 반환

요구사항:

  1. LBYL 방식: in 키워드로 사전 검사
  2. EAFP 방식: 예외 처리 활용
  3. 두 방식의 장단점을 주석으로 설명

힌트: Python은 EAFP 스타일을 권장합니다.

# LBYL 방식
def get_value_lbyl(dictionary, key, default=None):
    """
    LBYL: Look Before You Leap
    "뛰기 전에 살펴보라"
    
    장점:
    - 
    
    단점:
    - 
    """
    # 여기에 코드를 작성하세요
    pass


# EAFP 방식
def get_value_eafp(dictionary, key, default=None):
    """
    EAFP: Easier to Ask Forgiveness than Permission
    "허락보다 용서가 쉽다"
    
    장점:
    - 
    
    단점:
    - 
    """
    # 여기에 코드를 작성하세요
    pass


# 테스트
test_dict = {'name': 'Alice', 'age': 25}

문제 9: 종합 - 사용자 입력 검증 시스템

사용자 등록 시스템을 만들되, 다양한 예외 처리 기법을 모두 활용하세요.

요구사항:

  1. 이메일 형식 검증 (@ 포함 여부)
  2. 비밀번호 강도 검증 (최소 8자, 숫자 포함)
  3. 나이 범위 검증 (18~100)
  4. 모든 검증 실패 시 사용자 정의 예외 발생
  5. 검증 성공 시 사용자 정보 딕셔너리 반환
  6. 파일에 사용자 정보 저장 (context manager 사용)

사용자 정의 예외: - InvalidEmailError - WeakPasswordError - InvalidAgeError

# 사용자 정의 예외들
class InvalidEmailError(Exception):
    pass

class WeakPasswordError(Exception):
    pass

class InvalidAgeError(Exception):
    pass


def validate_email(email):
    """이메일 형식 검증"""
    # 여기에 코드를 작성하세요
    pass


def validate_password(password):
    """비밀번호 강도 검증"""
    # 여기에 코드를 작성하세요
    pass


def validate_age(age):
    """나이 범위 검증"""
    # 여기에 코드를 작성하세요
    pass


def register_user(email, password, age, filename="users.txt"):
    """
    사용자 등록 함수
    
    Args:
        email: 이메일 주소
        password: 비밀번호
        age: 나이
        filename: 저장할 파일명
    
    Returns:
        사용자 정보 딕셔너리 또는 None (실패 시)
    """
    try:
        # 1. 모든 검증 수행
        
        # 2. 사용자 정보 딕셔너리 생성
        user = {
            'email': email,
            'age': age
        }
        
        # 3. 파일에 저장 (with 문 사용)
        
        print(f"✓ 사용자 등록 성공: {email}")
        return user
    
    except (InvalidEmailError, WeakPasswordError, InvalidAgeError) as e:
        print(f"✗ 등록 실패: {e}")
        return None
    
    except IOError as e:
        print(f"✗ 파일 저장 실패: {e}")
        return None


# 테스트

문제 10**: 커스텀 Context Manager 만들기

with 문과 함께 사용할 수 있는 자신만의 context manager를 만드세요.

시나리오:

코드 실행 시간을 측정하고, 특정 시간 이상 걸리면 경고를 출력하는 context manager를 만듭니다.

요구사항:

  1. contextlib.contextmanager 데코레이터 사용
  2. 코드 블록 실행 전 시작 시간 기록
  3. 코드 블록 실행 후 경과 시간 계산
  4. 임계값 초과 시 경고 메시지
  5. 예외 발생 시에도 경과 시간 출력
  6. 선택적으로 코드 블록의 이름 지정
import time
from contextlib import contextmanager

@contextmanager
def performance_monitor(task_name="작업", threshold=1.0):
    """
    코드 블록의 실행 시간을 측정하는 context manager
    
    Args:
        task_name: 작업 이름
        threshold: 경고 임계값 (초)
    
    Usage:
        with performance_monitor("데이터 처리", threshold=2.0):
            # 시간이 오래 걸리는 작업
            time.sleep(3)
    """
    # 여기에 context manager 구현


# 테스트 1: 정상 실행
print("=== 테스트 1: 빠른 작업 ===")
with performance_monitor("빠른 계산", threshold=1.0):
    result = sum(range(1_000_000))

# === 테스트 1: 빠른 작업 ===
# [빠른 계산] 시작
# [빠른 계산] 종료 (경과: 0.009s)
# [빠른 계산] 모니터링 종료

# 테스트 2: 느린 작업 (경고 발생)
print("=== 테스트 2: 느린 작업 ===")
with performance_monitor("느린 계산", threshold=0.5):
    time.sleep(1)  # 1초 대기: 가상 작업 시뮬레이션

# === 테스트 2: 느린 작업 ===
# [느린 계산] 시작
# [느린 계산] 종료 (경과: 1.005s)
# ⚠️ 경고: 임계값 0.5s 초과
# [느린 계산] 모니터링 종료

# 테스트 3: 예외 발생
print("=== 테스트 3: 예외 발생 ===")
with performance_monitor("오류 발생 작업", threshold=1.0):
    time.sleep(0.5)
    2 / 0  # 강제로 예외 발생

# === 테스트 3: 예외 발생 ===
# [오류 발생 작업] 시작
# [오류 발생 작업] 경과: 0.505s
# [오류 발생 작업] 예외 발생 → ZeroDivisionError
# [오류 발생 작업] 모니터링 종료
Chapter 9: Functions
Chapter 15: Classes

This work © 2025 by Sungkyun Cho is licensed under CC BY-NC-SA 4.0