10. 프로그램 오류와 예외 처리#

프로그램을 구현하다보면 여러 종류의 오류를 경험한다. 파이썬 프로그래밍 중에 가장 많이 발생하는 오류와 대처방안을 소개한다.

10.1. 프로그램 오류와 디버깅#

프로그램의 오류는 크게 세 가지 유형으로 나뉜다. 오류가 발생하면 프로그램 개발자는 오류의 원인을 찾아 해결해야 한다. 이렇게 프로그램의 오류를 찾아 해결하는 과정을 디버깅debugging이라 부르며, 파이썬 실행기는 오류가 발생할 경우 디버깅에 도움되는 정보를 제공한다.

10.1.1. 구문 오류#

변수 이름으로 예를 들어 False, if, return 등 파이썬에서 특별한 기능을 갖는 키워드를 사용하거나, bad name, odd~job, US$ 등처럼 변수 이름짓기에 허용되지 방식을 사용하면 SyntaxError: invalid syntax와 같은 구문 오류syntax error가 발생한다.

bad name = 5
  Cell In[1], line 1
    bad name = 5
        ^
SyntaxError: invalid syntax

구문 오류는 명령문 구성 문법에 어긋난다는 의미이며 파이썬 실행기가 프로그램을 실행하기 전에 일종의 문법 검사를 통해 찾아낸다.

10.1.2. 런타임 오류#

런타임 오류runtime error는 프로그램이 실행되는 도중에 발생하는 오류이다. 프로그램의 실행을 중단시키는 예외 상황이라는 의미에서 런타임 오류를 예외exception라고 부르기도 한다. 런타임 오류는 다양한 경우에 발생한다. 여기서는 네 종류의 런타임 오류를 살펴본다.

런타임 오류 예제 1: 0으로 나누기 오류

런타임 에러의 대표적인 예는 0으로 나누기 오류다. ZeroDivisionError 라고 표시되는데, 이는 num이 0을 가리키고 있기에 결국 0으로 나눗셈을 시도하기 때문에 발생한다.

num = 0
print(3 / num)
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
Cell In[2], line 2
      1 num = 0
----> 2 print(3 / num)

ZeroDivisionError: division by zero

참고로 3/num은 구문syntax 측면에서는 전혀 문제 없다. 이유는 실제로 3/num이 계산될 때까지는 변수 num에 할당된 값이 0이 될 수도, 되지 않을 수도 있기에 겉모양만 보고는 실행이 가능한지 여부를 알 수 없기 때문이다.

런타임 오류 예제 2: 변수 이름 오류

아래 코드를 실행하면 a_number 라는 변수가 선언되어 있지 않기 때문에 문제가 발생한다. NameError라고 표시되는데, 이 오류는 선언되지 않은 변수가 사용될 때 발생한다. 자세히 살펴보면 a_numbera_Number 둘 중에 하나는 오타임을 알 수 있다.

a_Number = 327.68
b = a_number * 3
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[3], line 2
      1 a_Number = 327.68
----> 2 b = a_number * 3

NameError: name 'a_number' is not defined

런타임 오류 예제 3: 자료형 오류

아래 코드는 float() 함수를 리스트와 함께 호출한다. 그런데 float() 함수는 str, int, float 세 종류의 자료형만 인자로 사용하기로 정의되어 있다. 따라서 함수 인자의 자료형이 틀렸다는 의미의 TypeError가 발생하며 코드의 실행이 멈춘다. 따라서 마지막 print() 함수의 호출은 실행되지 않는다.

x = [1, 2]

print(float(x))
print("프로그램 실행 완료!")
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[4], line 3
      1 x = [1, 2]
----> 3 print(float(x))
      4 print("프로그램 실행 완료!")

TypeError: float() argument must be a string or a real number, not 'list'

런타임 오류 예제 4: 값의 형식 오류

아래 코드는 float() 함수를 123f2라는 문자열을 가리키는 변수 x와 함께 호출한다. 인자의 자료형이 str 이기에 float() 함수의 인자로 사용될 자격이 있다. 하지만 지정된 문자열이 부동소수점 형식이 아니기에 인자의 형식이 올바르지 않다는 의미의 ValueError가 발생하며 코드의 실행이 멈춘다. 따라서 마지막 print() 함수의 호출은 실행되지 않는다.

x = '123f2'

print(float(x))
print("프로그램 실행 완료!")
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[5], line 3
      1 x = '123f2'
----> 3 print(float(x))
      4 print("프로그램 실행 완료!")

ValueError: could not convert string to float: '123f2'

10.1.3. 의미 오류#

프로그램은 오류 없이 잘 실행되지만 원하는 결과를 만들지 못한다면 프로그램 어딘가에 논리적 오류가 존재한다는 의미이다. 이와같은 오류를 의미 오류semantic error라 부르며 발생원인은 매우 다양하다.

프로그램이 정상적으로 작동하기 때문에 구문 오류나 런타임 오류와는 다르게 오류의 원인을 바로 확인하기 어려울 수도 있다. 수학 문제를 풀다가 계산 실수를 하거나, 기호를 다르게 적거나, 문제를 잘못 이해했거나 해서 풀 수 있었던 문제를 틀렸던 경험이 있을 것이다. 프로그래밍에서도 유사한 실수가 많이 발생한다.

예를 들어, 아래 프로그램은 두 배 계산 대신에 제곱 계산을 하는 실수를 범한 경우이다.

num = 3
doubleNum = num ** 2

print("입력한 값", num, "의 두 배는", doubleNum, "입니다.")
입력한 값 3 의 두 배는 9 입니다.

두 배하려면 num * 2를 사용했어야 하는데 실수로 num ** 2를 사용하여서 결과적으로 입력한 값의 제곱을 계산하게 된다. 수천, 수만 줄로 이루어진 프로그램에서 이런 형식의 오류의 원인을 찾는 일은 경우에 따라 모래사장에서 바늘찾기처럼 매우 어렵거나 불가능할 수 있다.

10.2. 예외 처리#

오류가 발생할 수 있는 상황에 미리 대비하는 것이 예외 처리exception handling이며, 아래 형식을 따른다.

try:
    code1
except:
    code2

의미는 다음과 같다.

  • 먼저 try 키워드의 본문인 code1 이 실행된다. 실행 과정 중에 오류가 발생하지 않으면 except 키워드의 본문인 code2 부분은 건너 뛴다.

  • 만약 code1 실행중에 오류가 발생하면 바로 이어서 code2 를 실행한다.

예를 들어 아래 코드는 ValueError로 인해 코드 실행이 멈추는 대신 입력값에 어떤 문제가 있는지 정보를 출력한다. 이유는 try의 본문에 있는 코드가 실행할 때 오류가 발생하면 바로 except의 본문에 있는 코드가 실행되도록 약속되었기 때문이다. 또한 코드의 실행은 계속 이어져서 마지막 줄의 print() 함수도 호출되어 프로그램이 제대로 실행되었음을 확인해준다.

x = '123f2'

try:
    print(float(x))
except:
    print('부동소수점 모양의 문자열이 아닙니다.')
    
print("프로그램 실행 완료!")
부동소수점 모양의 문자열이 아닙니다.
프로그램 실행 완료!

오류 종류를 지정하는 예외 처리

특정 종류의 오류가 발생할 때만 예외 처리를 실행하도록 할 수 있다. 예를 들어 아래 형식은 ValueError가 발생할 때만 예외 처리를 실행한다.

try:
    code1
except ValueError:
    code2

따라서 ValueError가 발생해서 예외 처리를 한 이전 코드를 다음과 같이 작성해도 동일한 결과를 얻는다.

x = '123f2'

try:
    print(float(x))
except ValueError:
    print('부동소수점 모양의 문자열이 아닙니다.')
    
print("프로그램 실행 완료!")
부동소수점 모양의 문자열이 아닙니다.
프로그램 실행 완료!

그런데 오류의 종류를 명시하면 다른 종류의 오류는 처리하지 못한다. 예를 들어 아래 코드는 float() 함수의 인자로 튜플이 입력되어 처리할 수 없어서 TypeError가 실행중에 발생한다. 그런데 ValueError만 처리하도록 설정되어 있어서 예외 처리가 제대로 실행되지 못하고 결국에 프로그램 실행이 중간에 멈춰버린다.

x = [1, 2]

try:
    print(float(x))
except ValueError:
    print('부동소수점 모양의 문자열이 아닙니다.')
    
print("프로그램 실행 완료!")
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[9], line 4
      1 x = [1, 2]
      3 try:
----> 4     print(float(x))
      5 except ValueError:
      6     print('부동소수점 모양의 문자열이 아닙니다.')

TypeError: float() argument must be a string or a real number, not 'list'

이에 대한 해결책은 오류의 종류를 명시하지 않거나 아래처럼 여러 종류의 오류를 처리하도록 지정하는 것이다.

x = [1, 2]

try:
    print(float(x))
except (ValueError, TypeError):
    print('사용된 인자에 문제가 있습니다.')

print("프로그램 실행 완료!")    
사용된 인자에 문제가 있습니다.
프로그램 실행 완료!

10.3. 필수 예제#

참고: (필수 예제) 프로그램 오류와 예외 처리

10.4. 연습문제#

참고: (실습) 프로그램 오류와 예외 처리