def safe_divide(a, b):
"""
안전한 나눗셈 함수
Args:
a: 피제수
b: 제수
Returns:
나눗셈 결과 또는 None (오류 시)
"""
# 여기에 코드를 작성하세요
pass
# 테스트
Exercises: Chapter 14
Chapter 14: Exceptions - 연습문제
문제 1: 기본 예외 처리
사용자로부터 두 개의 숫자를 입력받아 나눗셈을 수행하는 함수를 작성하세요.
요구사항:
- 0으로 나누기 시도 시 적절한 메시지 출력
- 숫자가 아닌 값 입력 시 적절한 메시지 출력
- 정상적으로 실행되면 결과 반환
- 모든 경우에 “연산 시도 완료” 메시지 출력
힌트: try, except, else, finally 블록을 모두 활용하세요.
# 테스트
safe_divide(10, 2) # 5.0
safe_divide(10, 0) # "0으로 나눌 수 없습니다"
safe_divide(10, 'a') # "숫자만 입력 가능합니다"문제 2: 여러 예외 타입 처리
리스트에서 특정 인덱스의 요소를 가져오는 함수를 작성하되, 발생 가능한 여러 예외를 처리하세요.
처리해야 할 예외:
IndexError: 인덱스가 범위를 벗어난 경우TypeError: 첫 번째 인자가 리스트가 아니거나, 두 번째 인자가 정수가 아닌 경우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: 파일 처리와 예외
파일을 안전하게 읽어오는 함수를 작성하세요.
요구사항:
- 파일이 없으면 “파일을 찾을 수 없습니다” 메시지와 함께 빈 문자열 반환
- 권한이 없으면 “파일 읽기 권한이 없습니다” 메시지와 함께 빈 문자열 반환
- 인코딩 오류 발생 시 다른 인코딩(cp949)으로 재시도
- 그 외 I/O 오류는 일반 오류 메시지 출력
힌트: FileNotFoundError, PermissionError, UnicodeDecodeError, IOError를 처리하세요.
def safe_read_file(filename):
"""
파일을 안전하게 읽어오는 함수
Args:
filename: 파일 경로
Returns:
파일 내용 또는 빈 문자열 (오류 시)
"""
# 여기에 코드를 작성하세요
pass
# 테스트
문제 4: 사용자 정의 예외
재고 관리 시스템을 구현하되, 사용자 정의 예외를 만들어 사용하세요.
요구사항:
InvalidProductError: 존재하지 않는 상품을 주문할 때InvalidQuantityError: 수량이 0 이하일 때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 문을 사용하여 개발 중 버그를 빠르게 발견할 수 있도록 하세요.
요구사항:
- 학생 데이터의 타입과 값을 검증
- 이름은 비어있지 않은 문자열
- 나이는 0~150 사이의 정수
- 점수는 0~100 사이의 숫자 리스트
- 점수 리스트는 비어있지 않음
힌트: 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)
데이터베이스 연결을 시뮬레이션하는 함수를 작성하되, 예외를 잡아서 로깅한 후 다시 발생시키세요.
요구사항:
- 연결 시도 시 로그 메시지 출력
- 예외 발생 시 오류 로그 출력
- 예외를 다시 상위로 전파 (re-raise)
- 연결 종료 시도는 항상 수행
힌트: 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
요구사항:
ZeroDivisionError는 개별적으로 처리- 나머지
ArithmeticError는 한 번에 처리 - 각 경우에 적절한 메시지 출력
힌트: 구체적인 예외를 먼저, 일반적인 예외를 나중에 배치하세요.
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) 두 가지 방식으로 구현하고 차이를 비교하세요.
기능: 딕셔너리에서 키의 값을 가져오되, 없으면 기본값 반환
요구사항:
- LBYL 방식:
in키워드로 사전 검사 - EAFP 방식: 예외 처리 활용
- 두 방식의 장단점을 주석으로 설명
힌트: 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: 종합 - 사용자 입력 검증 시스템
사용자 등록 시스템을 만들되, 다양한 예외 처리 기법을 모두 활용하세요.
요구사항:
- 이메일 형식 검증 (@ 포함 여부)
- 비밀번호 강도 검증 (최소 8자, 숫자 포함)
- 나이 범위 검증 (18~100)
- 모든 검증 실패 시 사용자 정의 예외 발생
- 검증 성공 시 사용자 정보 딕셔너리 반환
- 파일에 사용자 정보 저장 (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를 만듭니다.
요구사항:
contextlib.contextmanager데코레이터 사용- 코드 블록 실행 전 시작 시간 기록
- 코드 블록 실행 후 경과 시간 계산
- 임계값 초과 시 경고 메시지
- 예외 발생 시에도 경과 시간 출력
- 선택적으로 코드 블록의 이름 지정
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
# [오류 발생 작업] 모니터링 종료