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. 언제 잡고 언제 전파할 것인가¶
유용한 경험칙:
의미 있게 복구할 수 있는 수준에서 예외를 잡으십시오 – 기본값으로 대체하거나, 재시도하거나, 잘못된 입력을 건너뜁니다.
크래시 말고는 할 수 있는 유용한 일이 없을 때, 또는 복구하는 방법을 아는 것이 위쪽 계층일 때는 예외가 전파되도록 두십시오.
호출 스택 중간에 있는 함수가 모든 오류를 삼키고 조용히 반환하면 실패를 추적할 수 없게 됩니다. 예외가 정말로 대처 방안을 가진 코드에 도달할 때까지 전달되도록 두는 것을 선호하십시오.