Repository navigation
KR_Exception
somaz edited this page Sep 22, 2026
·
9 revisions
예외 처리는 프로그램 실행 중 발생할 수 있는 오류를 처리하는 방법이다.
try:
# 실행할 코드
result = 10 / 0
except ZeroDivisionError as e:
# 예외 발생시 실행할 코드
print(f"에러 발생: {e}")
else:
# 예외가 발생하지 않았을 때 실행할 코드
print("성공!")
finally:
# 예외 발생 여부와 관계없이 실행할 코드
print("종료")# 기본 예외 처리
try:
num = int(input("숫자를 입력하세요: "))
result = 100 / num
print(f"결과: {result}")
except ValueError:
print("올바른 숫자를 입력하세요.")
except ZeroDivisionError:
print("0으로 나눌 수 없습니다.")예외 처리 흐름:
-
try블록의 코드가 실행된다. - 예외가 발생하면 해당 예외와 일치하는
except블록이 실행된다. - 일치하는
except가 없으면 예외는 상위 호출자로 전파된다. - 예외가 발생하지 않으면
else블록이 실행된다. - 마지막으로
finally블록이 항상 실행된다.
def safe_division(a, b):
try:
result = a / b
except ZeroDivisionError:
print("0으로 나눌 수 없습니다.")
return None
except TypeError:
print("숫자만 나눌 수 있습니다.")
return None
else:
print("나눗셈 성공!")
return result
finally:
print("나눗셈 연산 종료")
# 테스트
print(safe_division(10, 2)) # 정상 케이스
print(safe_division(10, 0)) # 0으로 나누기
print(safe_division(10, "2")) # 타입 에러Python은 다양한 내장 예외 클래스를 제공한다. 모든 예외는 BaseException 클래스에서 파생된다.
# 기본적인 내장 예외들
try:
# IndexError
list1 = [1, 2, 3]
print(list1[4])
except IndexError:
print("리스트 인덱스 범위 초과")
try:
# KeyError
dict1 = {"a": 1}
print(dict1["b"])
except KeyError:
print("존재하지 않는 키")
try:
# ValueError
int("abc")
except ValueError:
print("잘못된 값 변환")- Exception: 대부분의 내장 예외의 기본 클래스
-
ArithmeticError: 모든 산술 연산 관련 오류의 기본 클래스
- ZeroDivisionError: 0으로 나누기 시도
- OverflowError: 연산 결과가 너무 큼
-
LookupError: 매핑 또는 시퀀스 관련 오류의 기본 클래스
- IndexError: 시퀀스에서 범위를 벗어난 인덱스
- KeyError: 매핑(딕셔너리)에서 존재하지 않는 키
- ValueError: 값이 적절하지 않음 (올바른 타입이지만 부적절한 값)
- TypeError: 부적절한 타입의 객체에 연산 적용
- NameError: 존재하지 않는 변수 참조
- AttributeError: 객체에 존재하지 않는 속성 참조
- SyntaxError: 파이썬 문법 오류 (컴파일 시간 예외)
- RuntimeError: 프로그램 실행 중 발생한 일반적인 오류
- FileNotFoundError: 파일이 존재하지 않음
- PermissionError: 파일 접근 권한 부족
- ImportError/ModuleNotFoundError: 모듈 가져오기 실패
- MemoryError: 메모리 부족
- RecursionError: 최대 재귀 깊이 초과
# 예외 타입 계층 구조 확인
def print_exception_hierarchy(exception_class, indent=0):
print(' ' * indent + exception_class.__name__)
for subclass in exception_class.__subclasses__():
print_exception_hierarchy(subclass, indent + 4)
# 일부 예외 계층 구조 출력
print_exception_hierarchy(BaseException)
# 상속 관계를 이용한 예외 처리
try:
# 다양한 산술 예외 가능성이 있는 코드
result = 1 / 0
except ArithmeticError as e:
print(f"수학 연산 오류: {type(e).__name__} - {e}")표준 라이브러리의 내장 예외만으로 모든 오류 상황을 표현하기 어려울 때 사용자 정의 예외를 만들 수 있다.
class CustomError(Exception):
def __init__(self, message):
self.message = message
def __str__(self):
return self.message
def validate_age(age):
if age < 0:
raise CustomError("나이는 음수일 수 없습니다")
return age# 기본 예외 클래스
class AppError(Exception):
"""애플리케이션 기본 예외"""
def __init__(self, message="애플리케이션 오류가 발생했습니다", code=None):
self.message = message
self.code = code
super().__init__(self.message)
# 특정 기능별 예외
class ValidationError(AppError):
"""입력값 검증 예외"""
def __init__(self, message="입력값이 유효하지 않습니다", field=None):
self.field = field
message_with_field = f"{message} (필드: {field})" if field else message
super().__init__(message_with_field, code="VALIDATION_ERROR")
class DatabaseError(AppError):
"""데이터베이스 작업 예외"""
def __init__(self, message="데이터베이스 오류", operation=None):
self.operation = operation
message_with_op = f"{message} (작업: {operation})" if operation else message
super().__init__(message_with_op, code="DB_ERROR")
# 예외 사용
def register_user(username, age):
if not username:
raise ValidationError("사용자 이름은 필수입니다", "username")
if not isinstance(age, int) or age < 0 or age > 150:
raise ValidationError("나이는 0-150 사이의 정수여야 합니다", "age")
# 데이터베이스 저장 시뮬레이션
if username == "admin":
raise DatabaseError("사용자 저장 실패", "INSERT")
return {"username": username, "age": age}
# 예외 처리
try:
user = register_user("", 25)
except ValidationError as e:
print(f"검증 오류: {e.message}, 코드: {e.code}, 필드: {e.field}")
except DatabaseError as e:
print(f"DB 오류: {e.message}, 코드: {e.code}, 작업: {e.operation}")
except AppError as e:
print(f"일반 오류: {e.message}, 코드: {e.code}")# 여러 예외 처리
try:
value = input("값을 입력하세요: ")
num = int(value)
result = 100 / num
print(f"결과: {result}")
except (TypeError, ValueError) as e:
# 여러 예외를 한번에 처리
print(f"타입 또는 값 에러: {e}")
except ZeroDivisionError as e:
print(f"0으로 나눌 수 없습니다: {e}")
except Exception as e:
# 모든 예외 처리 (마지막 예외 처리자로만 사용)
print(f"기타 에러: {e}")# 예외 회피 (일부러 무시)
def ignore_exception():
try:
# 실패해도 괜찮은 작업
risky_operation()
except Exception:
pass # 예외 무시
# 예외 다시 발생 (예외 체인)
def process_data(data):
try:
return process_value(data)
except ValueError as e:
# 추가 정보와 함께 예외 다시 발생
raise ValueError(f"데이터 처리 실패: {data}") from e
# 현재 예외 정보 접근
import sys
def log_exception():
try:
1 / 0
except:
# 현재 예외 정보 확인
exc_type, exc_value, exc_traceback = sys.exc_info()
print(f"예외 유형: {exc_type}")
print(f"예외 값: {exc_value}")
# 예외 추적 정보 출력
import traceback
traceback.print_exc() # 간단한 스택 추적 정보 출력def calculate_average(numbers):
# 조건 확인 및 실패 시 AssertionError 발생
assert len(numbers) > 0, "리스트가 비어있습니다"
return sum(numbers) / len(numbers)
# assert 사용 예
try:
avg = calculate_average([])
except AssertionError as e:
print(f"어설션 오류: {e}")Python에서는 보통 두 가지 코딩 스타일을 사용한다:
- EAFP (Easier to Ask for Forgiveness than Permission): 허락보다 용서가 쉽다
- LBYL (Look Before You Leap): 도약하기 전에 살펴본다
# LBYL 방식 (사전 검사)
def lbyl_style(data, key):
if key in data: # 먼저 확인
return data[key]
else:
return None
# EAFP 방식 (예외 처리)
def eafp_style(data, key):
try:
return data[key] # 일단 시도
except KeyError:
return None
# Python은 일반적으로 EAFP 스타일을 선호한다컨텍스트 관리자는 with 문과 함께 사용되며, 자원 관리를 자동화한다.
# with 문을 사용한 자원 관리
with open('file.txt', 'r') as f:
content = f.read()
# 파일이 자동으로 닫힘 (예외 발생해도 닫힘)# 클래스 기반 컨텍스트 관리자
class MyContextManager:
def __init__(self, name):
self.name = name
def __enter__(self):
print(f"{self.name} 컨텍스트 시작")
return self # with 문의 as 변수에 할당됨
def __exit__(self, exc_type, exc_val, exc_tb):
print(f"{self.name} 컨텍스트 종료")
# 예외 정보 출력
if exc_type is not None:
print(f"예외 발생: {exc_type.__name__} - {exc_val}")
# True 반환 시 예외를 처리한 것으로 간주
# False 또는 None 반환 시 예외를 호출자에게 전파
return False # 예외 전파
# 사용 예시
try:
with MyContextManager("테스트") as cm:
print("작업 수행 중...")
raise ValueError("테스트 예외")
print("이 줄은 예외가 발생하면 실행되지 않음")
except ValueError:
print("예외가 전파되었습니다.")from contextlib import contextmanager
@contextmanager
def my_context(name):
print(f"{name} 컨텍스트 시작")
try:
# yield 이전: __enter__에 해당
yield name # 컨텍스트 내부로 값 전달
# yield 이후: 정상 종료 시 __exit__에 해당
print(f"{name} 컨텍스트 정상 종료")
except Exception as e:
# 예외 발생 시 __exit__에 해당
print(f"{name} 컨텍스트 예외 종료: {e}")
# 다시 발생시키면 호출자에게 예외 전파
raise
# 사용 예시
with my_context("데코레이터") as value:
print(f"컨텍스트 내부, 값: {value}")
# 예외 발생 시 컨텍스트 관리자가 처리
# raise ValueError("테스트 예외")# 임시 디렉터리 컨텍스트 관리자
import tempfile
import shutil
import os
@contextmanager
def temporary_directory():
temp_dir = tempfile.mkdtemp()
print(f"임시 디렉터리 생성: {temp_dir}")
try:
yield temp_dir
finally:
print(f"임시 디렉터리 삭제: {temp_dir}")
shutil.rmtree(temp_dir)
# 데이터베이스 트랜잭션 컨텍스트 관리자 (가상)
@contextmanager
def transaction():
print("트랜잭션 시작")
try:
yield
print("트랜잭션 커밋")
except:
print("트랜잭션 롤백")
raise
# 사용 예시
with temporary_directory() as temp_dir:
# 임시 파일 생성
file_path = os.path.join(temp_dir, "test.txt")
with open(file_path, "w") as f:
f.write("테스트 데이터")
print(f"파일 생성됨: {file_path}")
# 컨텍스트 종료 시 임시 디렉터리와 파일 삭제# 세분화된 예외 처리
def process_user_input():
try:
# 작은 try 블록으로 특정 작업만 보호
age = int(input("나이를 입력하세요: "))
except ValueError:
print("나이는 숫자로 입력해야 합니다.")
return None
try:
# 다른 작업은 별도의 try 블록으로 보호
name = input("이름을 입력하세요: ")
if not name:
raise ValueError("이름은 필수입니다.")
except ValueError as e:
print(f"입력 오류: {e}")
return None
# 성공 시 결과 반환
return {"name": name, "age": age}
# 대비 - 너무 큰 try 블록 (안티 패턴)
def process_user_input_bad():
try:
# 너무 많은 로직을 하나의 try에 포함
age = int(input("나이를 입력하세요: "))
name = input("이름을 입력하세요: ")
if not name:
raise ValueError("이름은 필수입니다.")
return {"name": name, "age": age}
except Exception as e:
# 너무 일반적인 예외 처리
print(f"오류 발생: {e}")
return Noneimport logging
# 로깅 설정
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
def divide_numbers(a, b):
try:
result = a / b
logger.info(f"나눗셈 성공: {a} / {b} = {result}")
return result
except ZeroDivisionError:
# 예외 로깅 (적절한 레벨 선택)
logger.error(f"0으로 나누기 시도: {a} / {b}", exc_info=True)
# 사용자에게 친숙한 메시지 반환
return "0으로 나눌 수 없습니다"
except TypeError as e:
logger.exception(f"타입 오류: {a} / {b}") # 전체 스택 추적 로깅
return f"숫자만 나눌 수 있습니다: {e}"# 집중화된 오류 처리
def get_user_by_id(user_id):
try:
# 데이터베이스 액세스 코드
user = database.find_user(user_id)
return user
except DatabaseConnectionError:
log_error("데이터베이스 연결 실패")
raise ServiceUnavailableError("서비스가 일시적으로 불가능합니다")
except UserNotFoundError:
return None # 사용자가 없으면 None 반환
except Exception as e:
log_error(f"사용자 조회 중 알 수 없는 오류: {e}")
raise InternalServerError("내부 서버 오류가 발생했습니다")
# 조기 반환과 가드 조항 (Guard Clause)
def process_payment(payment):
# 전제 조건 검사 - 빠른 실패(Fail Fast) 전략
if not payment:
raise ValueError("결제 정보가 없습니다")
if not payment.get('amount'):
raise ValueError("결제 금액이 없습니다")
if payment.get('amount') <= 0:
raise ValueError("결제 금액은 양수여야 합니다")
# 전제 조건이 충족되면 주요 로직 수행
return process_valid_payment(payment)✅ 예외 처리 모범 사례:
- 구체적인 예외를 먼저 처리하고, 일반적인 예외를 나중에 처리한다
- 예외 계층 구조를 활용하여 관련 예외를 그룹화한다
- 최소한의 코드만 try 블록에 포함시켜 오류 원인을 명확히 한다
- 매우 큰 try-except 블록은 피하고 적절한 크기로 나눈다
- 비어 있는 except 블록은 특별한 이유가 없다면 사용하지 않는다
- 표준 라이브러리 예외를 재사용할 수 있다면 사용자 정의 예외 대신 사용한다
- 의미 있는 오류 메시지를 제공하고 가능하면 해결 방법도 제시한다
- 디버깅에 도움되는 정보를 로그에 기록한다
- 사용자 정의 예외는 의미 있는 계층 구조로 설계한다
- 자원 관리를 위해 항상 컨텍스트 관리자(with 문)를 사용한다