Effective Python C3. None보다는 예외를 반환

3 분 소요

Introduction

파이썬 개발자들은 처리가 실패하거나 처리할 값이 없을 경우 None을 반환하는 코드를 짜기도 한다. 그러나 None을 반환하도록 하면, 조건문에서 0, 빈문자열도 동일한 결과를 내므로 버그를 일으키기 쉽다.
또한 어떤 오류가 발생했는지도 알기 어렵다.
따라서 예외를 발생시키는 것이 좋다.

Best WAY 20: None을 반환하기보다는 예외를 발생시켜라.

예를 들어 나눗셈에 대한 결과를 반환하는 함수를 작성한다고 해보자.

# None을 반환
def careful_divide_none(a, b):
    try:
        return a / b
    except ZeroDivisionError as e:
        return None
  
# 결과 성공/실패여부와 결과를 반환
def careful_divide_success(a, b):
    try:
        return True, a / b
    except ZeroDivisionError as e:
        return False, None
    
# Exception을 반환
def careful_divide_exception(a, b):
    try:
        return a / b
    except ZeroDivisionError as e:
        raise ValueError('잘못된 입력')

None을 반환하는 경우, 실수로 다음과 같이 처리할 수 있다.

x, y = 0, 5
result = careful_divide_none(x, y)
if not result:
    print('잘못된 입력')

그러나 result가 0인 경우에도 ‘잘못된 입력’이 출력된다. 즉 예기치 못한 버그를 야기할 수 있다.

그래서 다음처럼 성공/실패 여부와 함께 반환하는 careful_divide를 사용했다 해보자.

x, y = 0, 5
success, result = careful_divide_success(x, y)
if not success:
    print('잘못된 입력')

동작은 잘 되겠지만, 코드를 짜면서 앞의 success를 무시하고 조건문을 안 넣는 가능성이 있다.

exception 문을 사용하면 아래와 같이 짤 수 있다.

x, y = 0, 5
try:
    result = careful_divide_exception(x, y)
except ValueError:
    print('잘못된 입력')
else:
    print(f'결과는 {result}')

코드가 좀 더 명확하다. 또한 예외 처리에 따른 분기도 처리할 수 있다. 만약 절대로 함수가 None값을 반환하지 않도록 한다면, 다음처럼 타입 애너테이션을 이용해 반환값이 float이라고 명시하면 된다.

def careful_divide(a: float, b: float) -> float:
    try:
        return a / b
    except ZeroDivisionError as e:
        raise ValueError('잘못된 입력')

Summary

  • 특별한 경우에 None을 반환하도록 하면 None이 아닌 0, 빈문자열의 경우에도 동일하게 평가될 수 있으므로 실수하기 쉽다.
  • None 대신 raise로 error를 발생시키도록 한다.
  • 함수가 특별한 경우를 포함하는 어떤 경우도 None을 반환하지 않는다는 것을 타입 애너테이션으로 명시할 수 있다.

Reference

파이썬 코딩의 기술 제 2판. - 브렛 슬리킨 지음 / 오현석 옮김