2.26. 오류 발생시키기#
함수는 예외를 발생(raise) 시켜 호출자에게 문제를 알릴 수 있습니다. 키워드는 raise 입니다:
def square_root(x):
if x < 0:
raise ValueError("square_root expects a non-negative number")
return x ** 0.5
square_root(-1) 을 호출하면 raise 줄에서 멈추고, 함수 밖으로 점프하여 호출자에서 일치하는 except 를 찾습니다. 어떤 호출자도 그것을 잡지 않으면 스크립트는 트레이스백과 함께 종료됩니다.
2.26.1. 센티넬을 반환하는 대신 발생시키는 이유#
“잘못된 입력”을 알리는 두 가지 방법:
# signal with a sentinel
def square_root_or_none(x):
if x < 0:
return None
return x ** 0.5
# raise an exception
def square_root(x):
if x < 0:
raise ValueError("...")
return x ** 0.5
보통은 예외 형태가 더 낫습니다:
호출자는 오류 경우를 의도적으로 처리해야 합니다 –
try를 쓰거나, 예외가 전파되도록 두는 것입니다. 센티넬은 잊어버리기 쉽고 정상적인 결과로 착각하기 쉽습니다.오류 메시지는 예외와 함께 전달됩니다. 센티넬 방식은 그 진단 정보를 다른 어딘가에 따로 붙여야 합니다.
처리되지 않은 예외에 대한 기본 동작은 문제가 된 호출을 가리키는 트레이스백과 함께 요란하게 크래시하는 것입니다. 조용한
None반환은 나중에 미묘한 버그가 됩니다.
“찾지 못함”이 일상적이고 예외적이지 않은 결과일 때만 센티넬을 활용하십시오 – dict.get() 이 없는 키에 대해 None 을 반환하는 것은 바로 조회가 때때로 실패할 것으로 예상되기 때문입니다.
2.26.2. 사용자 정의 예외 클래스#
호출자가 내장 오류와 구별하고 싶어 할 수 있는 문제를 발생시키려면 Exception 의 서브클래스를 정의하십시오:
class ConfigError(Exception):
pass
def load_config(path):
try:
f = open(path)
except OSError as e:
raise ConfigError("missing config file: " + path)
try:
load_config("settings.json")
except ConfigError as e:
print("startup failed:", e)
빈 class 본문도 괜찮습니다 – 호출자가 클래스로 잡기 때문에 중요한 것은 이름 자체입니다. 호출자가 한 블록에서 전체 부류를 잡고 싶어 할 수 있다면 관련 오류들을 공통 기반 아래 묶으십시오.
2.26.2.1. 다시 발생시키기#
except 블록 안의 빈 raise 는 현재 예외를 다시 발생시켜 다음 처리기로 전파되도록 합니다:
try:
do_work()
except Exception as e:
log(e)
raise # let it keep going
이것은 함수가 오류를 실제로 처리하지 않으면서 관찰 하고 싶을 때(로그를 남기거나, 횟수를 세거나, 부분적인 변경을 되돌릴 때) 알맞은 형태입니다.
2.26.3. 언제 잡고 언제 전파할 것인가#
유용한 경험칙:
의미 있게 복구할 수 있는 수준에서 예외를 잡으십시오 – 기본값으로 대체하거나, 재시도하거나, 잘못된 입력을 건너뜁니다.
크래시 말고는 할 수 있는 유용한 일이 없을 때, 또는 복구하는 방법을 아는 것이 위쪽 계층일 때는 예외가 전파되도록 두십시오.
호출 스택 중간에 있는 함수가 모든 오류를 삼키고 조용히 반환하면 실패를 추적할 수 없게 됩니다. 예외가 정말로 대처 방안을 가진 코드에 도달할 때까지 전달되도록 두는 것을 선호하십시오.