Python에서 traceback 없이 프로그램 깔끔하게 종료하기 — sys.exit()와 예외 처리

1. 문제 정의

CLI 도구나 스크립트를 실행하다 보면, 정상적으로 종료하려는데 traceback 덤프가 화면에 출력되는 상황이 발생합니다.

다음과 같은 질문이 대표적입니다.

에러 원문: “How to exit from Python without having an traceback dump on the output. I still want to be able to return an error code but I do not want to display the traceback log. I want to be able to exit using exit(number) without trace but in case of an Exception (not an exit) I want the trace.”

즉, 두 가지 요구가 동시에 존재합니다.

요구사항설명
오류 코드 반환exit(number) 로 종료 상태 코드를 반환하고 싶다
traceback 숨기기정상 종료 시 traceback 덤프가 출력되지 않게 하고 싶다
예외는 trace 유지실제 Exception이 발생한 경우에는 trace를 출력하고 싶다

핵심 증상: exit(number)를 호출해도 traceback이 출력된다.


2. 원인 탐구

exit(number)를 호출했는데 traceback이 보인다면, 문제의 원인은 exit() 자체가 아닙니다.

Python의 종료 흐름을 이해해야 합니다.

  • sys.exit() 또는 exit()SystemExit 예외를 발생시켜 인터프리터를 종료합니다.
  • 하지만 SystemExit이 아닌 다른 예외(예: ValueError, KeyError, RuntimeError 등)가 발생하고 그것이 처리되지 않으면, 인터프리터는 해당 예외의 traceback을 출력한 뒤 프로세스를 종료합니다.
  • 즉, exit(number)를 만났을 때 traceback이 나온 것이 아니라, 그 전에 이미 잡히지 않은 예외가 발생했기 때문에 traceback이 출력된 것입니다.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import sys

def main():
    # 예외가 발생하는 로직 (예: 존재하지 않는 키 접근)
    data = {"a": 1}
    print(data["b"])  # KeyError 발생!
    sys.exit(0)       # 이 코드는 실행되지 않음

if __name__ == "__main__":
    main()

위 코드를 실행하면 sys.exit(0)은 결코 도달하지 못하고, KeyError의 traceback이 출력된 뒤 비정상 종료됩니다.


3. 근본 원인 분석

근본 원인은 exit()가 아니라, catch되지 않은 예외입니다.

exit(number)SystemExit을 던질 뿐입니다. 문제는 프로그램을 종료하기 전에 처리되지 않은 예외가 발생해 traceback이 출력되는 것입니다.

따라서 올바른 관점은 다음과 같습니다.

“traceback을 어떻게 숨길까"가 아니라 “예외를 어디서 어떻게 잡느냐” 가 핵심이다.

체크 포인트를 표로 정리하면 다음과 같습니다.

현상원인접근
exit(n) 호출에도 traceback 출력exit() 도달 전 예외 발생예외를 try/except로 catch
정상 종료인데 trace 출력됨잡히지 않은 Exception이 종료 유발ExceptionKeyboardInterrupt 분리 catch
오류 코드는 반환하고 싶음traceback 없이도 코드 반환 필요catch 후 sys.exit(0) 호출

4. 코드 해결책

가장 간단하고 견고한 해법은 main() 함수에서 모든 예외를 catch하는 구조로 바꾸는 것입니다.

StackOverflow에서 채택된 최고 득표 답변은 다음과 같습니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import sys
import traceback

def main():
    try:
        # 메인 프로그램 로직 작성
        pass
    except KeyboardInterrupt:
        print("Shutdown requested...exiting")
    except Exception:
        traceback.print_exc(file=sys.stdout)
        sys.exit(0)

if __name__ == "__main__":
    main()

동작 설명:

  1. try: 블록에서 메인 프로그램의 로직을 실행합니다.
  2. except KeyboardInterrupt: — 사용자가 Ctrl+C를 눌렀을 때 traceback 없이 안내 문구만 출력하고 정상 종료합니다.
  3. except Exception: — 그 외의 예외가 발생하면 traceback.print_exc()로 trace를 원하는 곳(여기서는 sys.stdout)에 출력하고, sys.exit(0)으로 오류 코드를 반환하며 종료합니다.
  4. 예외가 발생하지 않는다면 traceback은 전혀 출력되지 않습니다.

file=sys.stdout을 지정하는 이유는, 기본적으로 traceback은 sys.stderr로 출력되는데, 출력 스트림을 명시적으로 제어하고 싶을 때 사용합니다.


5. 향후 예방 조치

같은 문제를 반복하지 않기 위한 예방 팁입니다.

예방 조치설명
main() 진입점 패턴 사용if __name__ == "__main__": main() 구조로 로직을 함수에 격리
try/except로 진입점 감싸기모든 예외를 잡는 최상위 예외 처리기 구성
KeyboardInterrupt 분리 처리Ctrl+C 상황과 일반 예외를 구분
trace 스트림 명시traceback.print_exc(file=sys.stderr) 등 출력 위치를 명확히 지정
SystemExit만 별도 처리sys.exit()와 구분해 SystemExit은 그대로 전파되도록 설계

정리하면, exit(number)로 종료할 때 traceback이 출력되는 것은 exit()의 문제가 아니라 잡히지 않은 예외 때문입니다. main()에서 try/except로 예외를 catch하고, 필요한 경우 trace를 출력한 뒤 sys.exit(0)을 호출하면, 오류 코드를 반환하면서도 traceback 없이 프로그램을 깔끔하게 종료할 수 있습니다.


출처