Exception
Exception이란?
예외는 프로그램 실행 중에 발생하는 오류 상황을 의미한다.
파일이 없거나, 잘못된 값을 넣거나, 리스트 범위를 벗어난 인덱스를 접근하면 예외가 발생한다.
Exceptions
| 예외 | 의미 |
|---|---|
ValueError |
값은 들어왔지만 값의 내용이 잘못된 경우 사용한다. |
TypeError |
값의 자료형이 잘못된 경우 사용한다. |
FileNotFoundError |
파일이나 폴더가 존재하지 않는 경우 사용한다. |
IndexError |
리스트, 튜플 등의 인덱스 범위를 벗어난 경우 발생한다. |
KeyError |
딕셔너리에 존재하지 않는 key를 접근한 경우 발생한다. |
ZeroDivisionError |
숫자를 0으로 나누려고 할 때 발생한다. |
AttributeError |
객체에 존재하지 않는 속성이나 메서드를 사용한 경우 발생한다. |
ImportError |
모듈을 불러오지 못한 경우 발생한다. |
ModuleNotFoundError |
존재하지 않는 모듈을 import하려고 한 경우 발생한다. |
PermissionError |
파일이나 폴더에 접근 권한이 없는 경우 발생한다. |
RuntimeError |
실행 중 일반적인 문제가 발생했지만 더 구체적인 예외가 애매할 때 사용한다. |
NotImplementedError |
아직 구현하지 않은 기능임을 표시할 때 사용한다. |
ValueError
자료형은 맞지만 값 자체가 잘못된 경우 사용한다.
interval_sec = -1
if interval_sec <= 0:
raise ValueError("interval_sec은 0보다 커야 한다.")
위 코드에서 interval_sec은 숫자이므로 자료형은 맞다.
하지만 초 단위 간격이 0 이하이면 의미상 잘못된 값이다.
따라서 ValueError를 사용한다.
타입은 맞는데 값의 범위나 의미가 잘못되었을 때 사용한다.
TypeError
값의 자료형이 잘못된 경우 사용한다.
interval_sec = "3"
if not isinstance(interval_sec, int):
raise TypeError("interval_sec은 int 타입이어야 한다.")
위 코드에서 "3"은 숫자처럼 보이지만 실제로는 문자열이다.
함수에서는 정수를 기대하는데 문자열이 들어왔으므로 TypeError를 사용한다.
ValueError와의 차이는 다음과 같다.
interval_sec = "3" # TypeError 대상
interval_sec = -3 # ValueError 대상
타입 자체가 잘못되었으면
TypeError, 타입은 맞지만 값이 이상하면ValueError를 사용한다.
FileNotFoundError
파일이나 폴더가 존재하지 않는 경우 사용한다.
from pathlib import Path
video_path = Path("video.mp4")
if not video_path.exists():
raise FileNotFoundError(f"비디오 파일이 존재하지 않는다: {video_path}")
비디오 분석 프로그램에서는 입력 영상이 반드시 필요하다.
따라서 영상 파일이 없으면 더 진행하면 안 된다.
이럴 때 FileNotFoundError를 사용한다.
from pathlib import Path
def extract_frames(video_path):
video_path = Path(video_path)
if not video_path.exists():
raise FileNotFoundError(f"비디오 파일이 존재하지 않는다: {video_path}")
print("프레임 추출을 시작한다.")
파일이 없으면 이후 코드가 정상적으로 실행될 수 없을 때 사용한다.
IndexError
리스트나 튜플에서 존재하지 않는 인덱스를 접근한 경우 발생한다.
frames = ["frame1", "frame2"]
print(frames[5])
frames에는 인덱스 0, 1만 존재한다.
그런데 5번 인덱스를 접근했으므로 IndexError가 발생한다.
직접 검사해서 예외를 발생시킬 수도 있다.
frames = ["frame1", "frame2"]
index = 5
if index >= len(frames):
raise IndexError("요청한 프레임 인덱스가 범위를 벗어났다.")
print(frames[index])
리스트, 튜플, 문자열처럼 순서가 있는 데이터에서 범위를 벗어난 위치를 접근할 때 발생한다.
KeyError
딕셔너리에 존재하지 않는 key를 접근한 경우 발생한다.
song = {
"title": "Butter",
"bpm": 120
}
print(song["artist"])
song 딕셔너리에는 "artist"라는 key가 없다.
따라서 KeyError가 발생한다.
없는 key일 수도 있다면 get()을 사용할 수 있다.
song = {
"title": "Butter",
"bpm": 120
}
artist = song.get("artist", "알 수 없음")
print(artist)
반드시 있어야 하는 key라면 직접 검사해서 예외를 발생시킬 수 있다.
song = {
"title": "Butter",
"bpm": 120
}
if "artist" not in song:
raise KeyError("song 딕셔너리에 artist 정보가 없다.")
딕셔너리에서 특정 key가 반드시 있어야 할 때 자주 확인한다.
ZeroDivisionError
숫자를 0으로 나누려고 할 때 발생한다.
total_frames = 100
duration = 0
fps = total_frames / duration
수학적으로 0으로 나누는 것은 불가능하다.
따라서 ZeroDivisionError가 발생한다.
영상 처리에서는 영상 길이가 0이면 FPS나 진행률 계산이 불가능하다.
total_frames = 100
duration = 0
if duration == 0:
raise ZeroDivisionError("duration이 0이므로 fps를 계산할 수 없다.")
fps = total_frames / duration
나눗셈을 하기 전에 분모가
0인지 확인해야 한다.
AttributeError
객체에 존재하지 않는 속성이나 메서드를 사용한 경우 발생한다.
name = "video.mp4"
print(name.exists())
exists()는 Path 객체에서 사용할 수 있는 메서드다.
하지만 name은 문자열이므로 exists() 메서드가 없다.
따라서 AttributeError가 발생한다.
올바른 코드는 다음과 같다.
from pathlib import Path
video_path = Path("video.mp4")
print(video_path.exists())
함수 안에서는 문자열로 들어온 경로를 Path 객체로 바꿔서 사용할 수 있다.
from pathlib import Path
def check_video(video_path):
video_path = Path(video_path)
if not video_path.exists():
raise FileNotFoundError(f"비디오 파일이 존재하지 않는다: {video_path}")
어떤 메서드를 쓰려면 그 메서드를 가진 객체인지 먼저 확인해야 한다.
ImportError
모듈을 불러오는 과정에서 문제가 생긴 경우 발생한다.
모듈 자체는 존재하지만, 그 안에서 특정 기능을 불러오지 못할 때 발생할 수 있다.
from math import not_exist_function
math 모듈은 존재하지만 not_exist_function은 존재하지 않는다.
따라서 ImportError가 발생한다.
선택적으로 기능을 불러오고 싶다면 다음처럼 처리할 수 있다.
try:
from math import not_exist_function
except ImportError:
print("해당 기능을 불러올 수 없다.")
모듈을 불러오는 과정에서 문제가 생겼을 때 사용한다.
ModuleNotFoundError
존재하지 않거나 설치되지 않은 모듈을 import하려고 할 때 발생한다.
import cv2
만약 opencv-python이 설치되어 있지 않으면 ModuleNotFoundError가 발생한다.
이 경우에는 패키지를 설치해야 한다.
pip install opencv-python
코드에서 직접 안내하려면 다음처럼 작성할 수 있다.
try:
import cv2
except ModuleNotFoundError:
raise ModuleNotFoundError(
"opencv-python이 설치되어 있지 않다. pip install opencv-python을 실행해야 한다."
)
모듈 자체가 없거나 설치되지 않았을 때 발생한다.
PermissionError
파일이나 폴더에 접근 권한이 없는 경우 발생한다.
with open("protected_file.txt", "w") as f:
f.write("hello")
해당 파일이 관리자 권한을 요구하거나 쓰기 권한이 없으면 PermissionError가 발생할 수 있다.
프로젝트에서는 결과 이미지를 저장할 폴더에 쓰기 권한이 없을 때 발생할 수 있다.
output_path = "frames/frame_001.txt"
try:
with open(output_path, "w") as f:
f.write("frame data")
except PermissionError:
print("파일을 저장할 권한이 없다.")
파일을 읽거나 쓰거나 삭제할 권한이 없을 때 발생한다.
RuntimeError
실행 중 문제가 발생했지만 더 적절한 예외가 애매할 때 사용한다.
model = None
if model is None:
raise RuntimeError("모델이 로드되지 않았다.")
예를 들어 악보 검출 모델이 준비되지 않았는데 검출을 실행하려고 하면 실행 상태가 잘못된 것이다.
def detect_sheet(frame, model):
if model is None:
raise RuntimeError("악보 검출 모델이 로드되지 않았다.")
print("악보 검출을 시작한다.")
다만 더 정확한 예외가 있다면 그 예외를 먼저 사용하는 것이 좋다.
if not video_path.exists():
raise FileNotFoundError("비디오 파일이 존재하지 않는다.")
if interval_sec <= 0:
raise ValueError("interval_sec은 0보다 커야 한다.")
파일 문제, 타입 문제, 값 문제처럼 명확히 분류하기 어려운 실행 문제에서 사용한다.
NotImplementedError
아직 구현하지 않은 기능임을 표시할 때 사용한다.
def detect_sheet_area(frame):
raise NotImplementedError("악보 영역 검출 기능은 아직 구현되지 않았다.")
함수 이름과 구조는 먼저 만들어두고, 실제 구현은 나중에 할 때 사용한다.
def detect_measure_change(prev_frame, current_frame):
raise NotImplementedError("마디 전환 감지 기능은 아직 구현되지 않았다.")
이렇게 작성하면 해당 함수를 실수로 호출했을 때 아직 구현되지 않았다는 사실을 명확하게 알 수 있다.
나중에 구현할 함수의 자리만 먼저 만들어둘 때 사용한다.
상황별 예외 선택 기준
| 상황 | 적절한 예외 |
|---|---|
| 타입이 잘못된 경우 | TypeError |
| 타입은 맞지만 값이 잘못된 경우 | ValueError |
| 파일이나 폴더가 없는 경우 | FileNotFoundError |
| 리스트 인덱스가 범위를 벗어난 경우 | IndexError |
| 딕셔너리에 없는 key를 접근한 경우 | KeyError |
| 숫자를 0으로 나누는 경우 | ZeroDivisionError |
| 객체에 없는 속성이나 메서드를 사용한 경우 | AttributeError |
| 모듈을 불러오는 과정에서 문제가 생긴 경우 | ImportError |
| 설치되지 않은 모듈을 import한 경우 | ModuleNotFoundError |
| 파일 접근 권한이 없는 경우 | PermissionError |
| 실행 중 애매한 문제가 발생한 경우 | RuntimeError |
| 아직 구현하지 않은 기능인 경우 | NotImplementedError |
프로젝트 코드 예시
from pathlib import Path
def extract_frames(video_path: Path, interval_sec: int):
if not isinstance(video_path, Path):
raise TypeError("video_path는 Path 타입이어야 한다.")
if not video_path.exists():
raise FileNotFoundError(f"비디오 파일이 존재하지 않는다: {video_path}")
if not isinstance(interval_sec, int):
raise TypeError("interval_sec은 int 타입이어야 한다.")
if interval_sec <= 0:
raise ValueError("interval_sec은 0보다 커야 한다.")
print("프레임 추출을 시작한다.")
위 코드의 예외 선택 기준은 다음과 같다.
| 코드 | 의미 | 예외 |
|---|---|---|
not isinstance(video_path, Path) |
video_path 타입이 잘못됨 |
TypeError |
not video_path.exists() |
파일이 존재하지 않음 | FileNotFoundError |
not isinstance(interval_sec, int) |
interval_sec 타입이 잘못됨 |
TypeError |
interval_sec <= 0 |
값의 범위가 잘못됨 | ValueError |