개발 · 자동화핵심 · 16분 읽기
Python 예외·자원 정리와 실패 기록
예외를 숨기지 말고 처리 가능한 실패만 구체적으로 다룹니다. 오류 원인과 정리 절차, 재시도 범위를 분리합니다.
개념부터 찾아보기
정의·역할·동작 원리를 독립 페이지에서 확인하세요. 본문의 밑줄 용어에도 설명이 있습니다.
이 노트에서 설명할 수 있게 되는 것
- 예외가 발생한 뒤 except·else·finally로 흐름이 바뀌는 순서를 설명한다.
- 처리 가능한 오류와 전파해야 할 오류를 나눈다.
- 정리 과정·재시도·로그에서 원래 실패 정보를 보존한다.
먼저 알면 좋은 개념Python 함수·인수·반복과 모듈
01예외 객체가 호출 경로를 거슬러 올라간다
- 실패·ValueError실행 중 int("abc")가 숫자 변환에 실패하면 ValueError 인스턴스가 발생한다.
- 처리기 탐색·전파현재 코드 위치에서 맞는 except를 찾고 없으면 호출자 쪽으로 전파한다.
- Traceback의 호출 경로처리되지 않으면 traceback과 예외 정보가 출력되며 일반 프로그램 실행이 중단될 수 있다. traceback은 호출 위치를 이어 보여 주므로 마지막 오류 줄만 읽기보다 어떤 입력이 어느 함수를 거쳐 왔는지 추적하는 자료다.
- ExceptionException 클래스 안에 발생 가능한 모든 오류가 목록으로 들어 있다는 설명은 정확하지 않다.
- 과 Except예외들은 클래스 상속 계층을 이루고 except SomeError는 그 클래스 또는 하위 클래스의 인스턴스를 처리한다.
- 아래의 KeyboardInterrupt와 SystemExit 등은 일반 Exception과 구분되므로 무조건 bare except로 삼키면 종료 요청까지 무시할 수 있다.
- 오류가 발생하는 시점문법 오류와 실행 중의 변환·접속·파일 오류를 구분한다.
- 진단 정보 노출traceback에는 경로와 요청 값 등이 포함될 수 있으므로 외부 응답과 내부 진단 기록의 범위를 나누고 운영 화면에 상세 traceback을 그대로 노출하지 않는다.
02try·except·else·finally의 정상과 실패 분기
- 의 문장을 순서대로 실행하다 예외가 생기면 남은 문장을 건너뛰고 맞는 except로 이동한다.
- 예외가 없으면 else를 수행하며 finally는 해당 구조를 빠져나갈 때 정리 작업을 수행한다.
- Else의 예외 범위else 안에서 생긴 예외는 앞의 에 대응하는 except가 자동으로 처리하는 범위가 아니므로 보호하려는 작업 범위를 좁게 정하는 데 의미가 있다.
- 정상·변환 실패 예아래 예시에서 숫자로 바뀌면 else가 출력하고 잘못된 형식이면 except가 설명한다.
- 어느 경로든 finally의 끝 메시지를 거친다.
- finally·returnfinally에서 return하거나 새 예외를 발생시키면 원래 결과·예외를 가릴 수 있어 정리 코드에는 이런 동작을 신중하게 둔다.
- ·except-except는 오류를 무조건 정상값으로 바꾸는 장치가 아니라 프로그램이 감당할 실패를 명시하는 제어 흐름이다.
text = "8080"
try:
port = int(text)
except ValueError:
print("정수 형식이 아니다")
else:
print("변환 결과:", port)
finally:
print("변환 시도 종료")
# 8080 입력: 변환 결과: 8080 -> 변환 시도 종료코드 동작을 설명하는 교육 예시. 주석의 결과는 언어 규칙으로 설명한 예상값이며 이 페이지 작성 중 실행하지 않았다.
03형식 변환 성공과 업무 조건 만족은 다르다
- ValueErrorint("70000")은 숫자 형식으로 변환할 수 있어 ValueError가 생기지 않는다.
- 하지만 / 포트 번호로는 허용 범위 밖이다.
- 상태·ValueError자료형 변환 후 업무 범위를 별도로 검사하고 잘못된 상태에 ValueError 또는 의미 있는 사용자 예외를 발생시키면 호출자가 구분하여 대응할 수 있다.
- 검증입력 검증과 외부 시스템 장애를 같은 메시지로 합치지 않는다.
- Exception사용자 정의 예외는 보통 Exception의 하위 클래스로 만들고 필요한 문맥을 담는다.
- 예외 메시지에는 개인키·암호·토큰을 넣지 않는다.
- 실패잘못된 호출을 숨기기 위해 무조건 False나 빈 list를 반환하면 진짜 빈 결과와 실패를 구분하기 어려워진다.
- 함수의 계약에 따라 정상값·예외 중 어떤 방식으로 전달할지 정한다.
class PortError(ValueError):
pass
def parse_port(text):
port = int(text)
if not 1 <= port <= 65535:
raise PortError("port must be between 1 and 65535")
return port
# parse_port("abc"): ValueError
# parse_port("70000"): PortError코드 동작을 설명하는 교육 예시. 주석의 결과는 언어 규칙으로 설명한 예상값이며 이 페이지 작성 중 실행하지 않았다.
04예외를 다시 발생시키고 원인을 연결하기
- except·raiseexcept 안의 raise만 사용하면 현재 처리 중인 예외를 다시 전파할 수 있다.
- raiseraise NewError(...) from exc는 바깥 계층에 맞는 오류를 만들면서 원래 원인을 명시적으로 연결한다.
- 예를 들어 파일을 읽는 저수준 OSError를 설정 로딩 실패라는 프로그램 의미로 감싸되 원래 경로·원인 추적을 유지하는 방식이다.
- 원인from None은 문맥 표시를 줄이지만 원인을 아예 모른 척하는 설계의 대체가 되어서는 안 된다.
- Exception구체적 예외를 먼저 두고 넓은 Exception을 뒤에 두어야 더 세밀한 처리 경로가 실행된다.
- ValueError·OSErrorValueError와 OSError는 입력 형식과 외부 I/O 실패라는 다른 범주다.
- 상태·원인잡은 예외를 아무 기록 없이 pass하면 이후 코드가 불완전한 상태로 계속되어 원인과 증상이 멀어질 수 있다.
def load_config(path):
try:
with open(path, encoding="utf-8") as stream:
return stream.read()
except OSError as exc:
raise RuntimeError("configuration could not be loaded") from exc코드 동작을 설명하는 교육 예시. 주석의 결과는 언어 규칙으로 설명한 예상값이며 이 페이지 작성 중 실행하지 않았다.
원리 심화
05예외 전파는 스택 정리와 원인 연결을 포함한다
- 전파되는 객체예외가 발생하면 오류 객체와 traceback이 실패 경로를 나타낸다. 호출자에 처리기가 없으면 더 바깥으로 전파되며 중간 함수의 finally 등 정리 경로를 거친다.
- 정상 후속 범위else는 가 예외 없이 정상적으로 끝난 경우의 후속 작업을 분리한다. 에서 return·break·continue로 빠져나가는 흐름까지 모두 else로 이어지는 것은 아니다.
- 대기 중인 반환에서 return할 값을 정했어도 finally 정리가 먼저 실행될 수 있다. 정리가 정상 종료되면 원래 반환 또는 예외 전파가 이어진다.
- 정리의 새 결과finally에서 새 return이나 예외를 발생시키면 기존 흐름을 대체하거나 가릴 수 있다. .14부터 finally를 빠져나가는 return 등에는 SyntaxWarning이 나올 수 있어 버전별 진단도 구분한다.
- raise AppError(...) from exc는 상위 오류와 직접 원인을 연결한다. 오류를 새 이름으로 바꿀 때 원인까지 잃으면 실제 실패 계층을 역추적하기 어렵다.
예제로 추적
06변환 오류를 업무 오류로 바꾸고 정리하는 흐름
- 입력 실패아래 예제의 int("bad")가 ValueError를 발생시키면 다음 정상 문장은 실행하지 않고 except ValueError로 이동한다.
- 의미 변환처리기는 설정값 해석 실패를 RuntimeError로 바꾸되 from exc로 원래 ValueError를 직접 원인으로 연결한다. 정상값을 만들지 않고 실패를 유지한다.
- 정리 수행새 오류가 전파되기 전에 finally의 cleanup이 출력된다. cleanup이 보인다고 설정 변환이나 함수 호출이 성공한 것은 아니다.
- 호출자 처리바깥 except RuntimeError가 새 예외를 받고 그 __cause__가 ValueError임을 읽는다. 예상 출력 순서는 cleanup 다음 RuntimeError ValueError다.
- 다른 실패 분기finally 자체에서 오류가 생기면 결과가 달라질 수 있다. 이 예제는 정리 출력이 정상 완료된다는 조건을 두고 원인 연결을 추적한다.
def parse_setting():
try:
return int("bad")
except ValueError as exc:
raise RuntimeError("invalid setting") from exc
finally:
print("cleanup")
try:
parse_setting()
except RuntimeError as exc:
print(type(exc).__name__, type(exc.__cause__).__name__)
# cleanup
# RuntimeError ValueError실행하지 않은 교육 예시. 주석의 출력은 언어 규칙으로 추적한 예상값이다.
검증·예외
07처리됨·전파됨·결과 불명을 따로 기록한다
- 매칭except Exception을 먼저 두면 그 아래의 구체적인 ValueError 처리기가 실행될 기회를 잃을 수 있다. 메시지 문자열 비교보다 타입과 상속 관계로 처리 범위를 정한다.
- 빈 값과 실패자료가 정말 0건인 경우와 조회가 실패해 빈 목록을 반환한 경우를 구별한다. 빈 값을 정상값으로 선택하려면 호출 계약에 그 의미를 명시해야 한다.
- timeout의 한계외부 변경 요청이 시간 초과되면 요청이 미실행인지 이미 반영됐는지 로컬 예외만으로 확정하지 못할 수 있다. 결과 불명 상태를 성공·실패와 별도로 남긴다.
- 재시도 전 상태반복해도 결과가 달라지지 않는 조회와 새 주문·계정 생성 같은 변경은 재시도 조건이 다르다. 작업 식별자와 상태 조회로 중복 여부를 판단한다.
- 원인 보존과 공개 범위내부에서는 예외 타입·연결된 원인·필요한 호출 위치를 남기고 사용자 응답에서는 민감 경로나 입력값을 제한한다. 오류를 숨기는 것과 공개 정보를 줄이는 것은 다른 작업이다.
08자원 정리와 재시도는 실패 종류에 맞춘다
- 자원의 정리 범위열린 파일·SSH 연결·잠금은 정상 완료와 오류 경로 양쪽에서 해제되어야 한다.
- withwith를 지원하면 로 수명을 표현하고, 그렇지 않으면 생성 성공 뒤 /finally에 close를 둔다.
- 실패자원 생성 자체가 실패한 경우 닫을 객체가 없다는 점까지 코드의 범위에 반영한다.
- 정리와 원래 오류 보고가 서로 방해하지 않게 한다.
- 다만 강제 종료나 시스템 장애까지 finally의 실행과 자원 정리가 보장되는 것은 아니다.
- 재시도재시도는 연결 시간 초과처럼 일시적 원인에 의미가 있을 수 있지만 인증 실패나 잘못된 설정을 계속 반복해도 해결되지 않는다.
- 쓰기 요청쓰기 요청은 응답만 잃었어도 서버에서 이미 수행되었을 수 있으므로 무조건 다시 실행하면 중복 작업이 된다.
- 제한된 재시도·상태 조회최대 시도 수·간격·멱등성 키·중간 상태 조회로 불확실성을 관리한다. 예외 처리는 성공으로 위장하기보다 시스템의 실제 상태를 정확히 전달하는 역할을 한다.
09실패를 결과로 관리하기
- 실패자동화에서는 성공·실패·미실행을 구분하고 대상, 시각, 오류 유형, 종료 코드를 필요한 범위에서 기록한다.
- 일시적인 연결 실패와 입력 오류는 대응이 다르다.
- 재시도재시도 횟수와 대기 시간을 제한하고 이미 반영된 변경을 반복해도 안전한지 확인한다.
value = "unknown"
try:
count = int(value)
except ValueError:
print("정수 형식이 아닙니다.")
else:
print(count)예상 가능한 자료 변환 오류만 구체적으로 처리하는 예시다.
10실패한 한 줄 다음에 무엇이 실행되는가
- 같은 안에서 문자열을 정수로 바꾸고 그 값을 사용하는 코드를 생각한다.
- ValueError·int("bad")에서 ValueError가 발생하면 의 남은 문장을 계속 실행하지 않고 대응하는 except를 찾는다.
- except·ValueErrorexcept ValueError가 있으면 그 처리로 이동하고, 이 에 대한 else는 실행하지 않는다.
- finally같은 구조의 finally는 일반적인 제어 흐름에서 예외 처리 이후 정리 단계로 실행된다.
- 정리예외가 처리되지 않으면 필요한 정리를 거쳐 바깥 호출자로 전파된다.
- elseelse를 쓰면 변환이 성공한 뒤 수행할 작업에서 생긴 예외를 앞의 변환 오류 처리와 섞지 않도록 범위를 나눌 수 있다.
- 예를 들어 파일 읽기까지 큰 하나에 넣고 모든 오류를 값이 잘못됐다고 보고하면 실제 권한 문제를 숨긴다.
- 여러 예외여러 예외를 처리할 때는 구체적인 유형과 의도를 분명히 하고 예외 메시지의 문자열 내용만으로 종류를 판정하지 않는다.
- 원인라이브러리의 상세 오류를 상위 업무 오류로 바꿀 때는 원인 예외를 연결해 진단 경로를 유지한다.
- 변환 시도int("bad")는 정상 정수 값을 반환하지 못한다.
- 예외 발생 안의 다음 문장은 실행하지 않는다.
- 구체적 처리일치하는 except가 처리하고 이 의 else는 건너뛴다.
- 정리finally가 정리 작업을 수행한다.
- 이후 흐름이 사례에서는 ValueError를 처리했으므로 finally 뒤의 후속 코드로 진행한다. 처리되지 않은 다른 예외라면 호출자로 전파된다.
설명과 함께 보는 보조 도식
처리 과정
ValueError가 발생한 호출의 제어 흐름
try·except ValueError·else·finally가 같은 구조에 있는 실패 경로를 보여 준다.
01try 실행문자열을 정수로 바꾸려 한다.
02ValueError변환 실패로 try의 남은 문장을 건너뛴다.
03맞는 except변환 오류를 처리한다.
04finally일반적인 흐름에서 정리 작업을 수행한다.
05후속·호출자처리됐으면 후속 작업, 아니면 바깥으로 전파
01
변환 시도
int("bad")는 정상 정수 값을 반환하지 못한다.
- 입력
- bad
전체 단계를 한 번에 읽기
- 01
변환 시도
int("bad")는 정상 정수 값을 반환하지 못한다.
- 입력
- bad
- 02
예외 발생
try 안의 다음 문장은 실행하지 않는다.
- 예외 유형
- ValueError
- 03
구체적 처리
일치하는 except가 처리하고 이 try의 else는 건너뛴다.
- else
- 실행하지 않음
- 04
정리
finally가 정리 작업을 수행한다.
- 주의
- 여기서 원래 예외를 덮어쓰지 않기
- 05
이후 흐름
이 사례에서는 ValueError를 처리했으므로 finally 뒤의 후속 코드로 진행한다. 처리되지 않은 다른 예외라면 호출자로 전파된다.
- 이 사례
- 예외 처리 후 계속 진행
11오류를 숨기는 처리와 정상 복구 구분하기
- exceptexcept에서 아무것도 하지 않고 지나가면 실패한 자료가 정상 결과처럼 이어질 수 있다.
- 기본값으로 대체하는 것도 업무에서 허용된 경우에만 해야 한다.
- 필수 설정필수 설정을 못 읽었는데 빈 설정으로 계속 실행하면 이후 단계에서 더 큰 오류를 만들 수 있다.
- 반대로 사용자 입력 한 항목의 변환 실패처럼 해당 항목만 보고하고 다른 입력을 계속 처리할 수 있는 경우도 있다.
- 실패처리 범위는 예외 문법보다 실제 작업의 원자성과 실패 허용 조건으로 결정한다.
- 정리·실패정리 함수 자체가 실패하면 원래 예외와 정리 예외를 구분해 기록해야 한다.
- finally·returnfinally에서 return하면 원래 반환값이나 예외 전달을 바꿀 수 있어 특별한 이유 없이 사용하지 않는다.
- 재시도재시도 로그에는 시도 횟수·대상·오류 유형·종료 상태를 남기고 비밀번호나 전체 요청 본문은 제외한다.
- 무한 재시도는 일시 장애를 자원 고갈로 키울 수 있다.
- 재시도·상태이미 변경이 적용된 뒤 응답만 잃은 경우에는 재시도 전에 상태를 조회해 중복 수행 가능성을 판단한다.
| 구성·관찰 대상 | 확인하는 값 | 구분할 문제 |
|---|---|---|
| 예외 위치 | 처음 실패한 연산 | 범위가 너무 넓은지 |
| 처리 분기 | 유형·대체값·전파 | 무시와 정상 복구의 차이 |
| 정리 단계 | 연결·파일·잠금 해제 | 원래 오류를 덮어쓰는지 |
| 재시도 | 횟수·간격·멱등성 | 중복 변경과 일시 오류 |
학습 확인
이해했는지 확인하기
- 처리할 예외 유형과 try 범위를 좁힌다.
- 실패해도 자원이 정리되는지 확인한다.
- 재시도 가능 오류와 입력·논리 오류를 구분한다.