계산 함수 결과가 갑자기 None이면 Python이 값을 잃어버린 것이 아니라, 실행된 경로에 return이 없었을 가능성이 크다. Python 함수는 명시적으로 반환하지 않으면 None을 반환한다.
def discount(price, member):
if member:
return price * 0.9
print(discount(100, False)) # Nonemember=True일 때는 90.0이 나오지만, False일 때는 조건문을 건너뛰고 함수 끝에 도달한다. 할인하지 않는 경우에도 가격을 돌려줘야 한다면 두 경로를 모두 설계해야 한다.
모든 경로의 반환을 확인한다
함수의 마지막 줄까지 도달하는 경로가 있는지 먼저 살펴보자. 아래 그림에서 값이 사라지는 지점은 계산식이 아니라 비회원 분기의 함수 끝이다.
위 함수가 회원이 아닐 때 원가를 반환해야 한다면 else 또는 마지막 return이 필요하다.
def discount(price, member):
if member:
return price * 0.9
return price수정한 뒤에는 성공 경로만 다시 보지 말고 빠졌던 경로를 함께 확인한다.
assert discount(100, True) == 90.0
assert discount(100, False) == 100값이 없는 입력까지 받을 수 있다면 그 처리도 먼저 정해야 한다. 모든 입력을 무심코 None으로 돌려주면 잘못된 가격과 단순한 미할인을 구별하기 어렵다.
print와 return은 다르다
함수 안에서 print(result)는 화면에 출력할 뿐 호출자에게 값을 전달하지 않는다. 테스트에서 반환값을 검증하려면 print 결과가 아니라 함수 호출 결과를 비교한다. 로그는 관찰용, return은 데이터 흐름용으로 분리하자.
def discount_message(price):
print(price * 0.9)
result = discount_message(100) # 화면에는 90.0이 출력된다.
assert result is None # 호출자는 값을 받지 못한다.디버거에서 호출 직후 결과가 None이라면 먼저 함수 안의 모든 if·return 경로를 따라가 보자. 출력문을 추가하기보다 분기별 반환값을 작은 테스트로 고정하면 같은 실수를 다시 잡기 쉽다. 계산 함수에 반환 타입을 적더라도 Python이 실행 시 자동으로 검사하지는 않는다. 정적 타입 검사와 분기 테스트를 함께 사용한다.
핵심 요약
명시적인 return이 없으면 Python 함수는 None을 반환한다. 조건문이 있는 계산 함수는 모든 경로가 값을 돌려주는지 테스트하고, print와 return의 역할을 섞지 말자. 타입 힌트를 더하면 None 가능성이 있는 API를 호출자에게 더 분명히 알릴 수 있다.

