파이썬 기초 강좌 #19 에러와 예외처리 (Error and Exception)

12 분 소요

안녕하세요. 스쿨오브웹의 이상희입니다. 오늘의 강좌에서는 에러와 예외처리에 대해서 공부하도록 하겠습니다. 친구에게 프로그램을 하나 만들어 줬는데 그 친구가 프로그램을 실행했더니 알 수도 없는 에러가 출력되면서 프로그램이 종료된다면 프로그램을 전혀 모르는 친구는 상당히 당황스러울겁니다. 프로그램이 에러를 발생시키면서 이상 종료되는 것을 보통 프로그램이 크래쉬했다고 하는데요, 이것을 막기 위해서 항상 에러가 발생될 가능성이 있는 부분에는 try, except 구문을 사용하여 예외처리를 해주셔야 합니다.

파이썬의 예외처리 구문에는 다음과 같은 기본 구조를 가지고 있습니다.

예외처리 구문의 기본 구조
try:
    ...
except:
    ...
else:
    ...
finally:
    ...
키워드설명
try기본 코드가 실행되는 블록입니다
except에러가 발생하였을 때 실행되는 블록입니다.
else어떤 에러도 발생하지 않았을 때 실행되는 블록입니다.
finally마지막에 항상 실행되는 블록입니다.

위의 기본 구조를 다시 간단히 설명을 드리겠습니다.

try 블록에는 실행하고자 하는 기본 코드를 입력하시면 됩니다. except 블록은 에러가 발생하였을 때 실행되는 블록입니다. else 블록은 어떤 에러도 발생하지 않았을 때 실행되는 블록입니다. finally 블록은 에러가 발생하거나, 발생하지 않아도 모든 코드가 실행된 다음에 마지막으로 실행되는 블록입니다.

아래의 코드는 사용자에게 두 개의 숫자를 입력 받아서 첫 번째 값에서 두 번째 값을 나눈 결과값을 출력하는 프로그램입니다. 실행해 보겠습니다.

파이썬 코드
num_1 = int(input('첫 번째 숫자: '))
num_2 = int(input('두 번째 숫자: '))
result = num_1 / num_2
print('{} / {} = {}'.format(num_1, num_2, result))
콘솔 출력
 번째 숫자: 4
 번째 숫자: 2
4 / 2 = 2.0

4와 2를 입력하고 문제없이 실행되었습니다. 만약에 사용자가 숫자가 아닌 문자를 입력하게 되면 어떻게 될까요?

프로그램을 실행하고 문자 "one"을 입력해 보겠습니다.

콘솔 출력
 번째 숫자: one
Traceback (most recent call last):
  File "/Users/curtis/Dev/work/function.py", line 1, in <module>
    num_1 = int(input('첫 번째 숫자: '))
ValueError: invalid literal for int() with base 10: 'one'

네 당연히 문자열을 정수로 변환할 수가 없으니 에러를 발생시키면서 프로그램이 이상 종료되었습니다. 이렇게 에러가 발생하면서 프로그램이 비정상적으로 종료하는 것을 막기위해서 예외처리를 해주셔야 하는데요, 기본적인 예외처리를 하기위해서는 tryexcept 키워드를 사용하시면 됩니다.

그럼 실제 코드를 만들어 보겠습니다. 우선 실행하고자 하는 코드를 try 블록에 넣습니다. 다음은 에러를 캐치하는 except 키워드를 사용합니다. except 키워드 뒤에는 캐치하고자 하는 에러 오브젝트의 이름을 입력합니다. 위의 에러 메세지를 잘 보시면 ValueError라는 에러 이름이 보이는데요, 이 에러 이름을 6번 줄 except 키워드 뒤에 입력하시면 됩니다. 그리고 이 에러가 발생했을 때 실행하고자 하는 코드를 except 보디 부분에 입력하시면 됩니다. 아래 코드를 실행해보겠습니다.

파이썬 코드
try:
    num_1 = int(input('첫 번째 숫자: '))
    num_2 = int(input('두 번째 숫자: '))
    result = num_1 / num_2
    print('{} / {} = {}'.format(num_1, num_2, result))
except ValueError as error:
    print('"ValueError"가 발생했습니다.')
    print(error)
콘솔 출력
 번째 숫자: one
"ValueError" 발생했습니다.
invalid literal for int() with base 10: 'one'

에러는 발생했지만, 예외처리가 잘 돼서 프로그램이 크래쉬 되지 않고 정상적으로 종료된 것을 볼 수 있습니다.

에러가 발생할 때마다 프로그램을 다시 실행해야 한다면 사용자 입장에서는 상당히 귀찮은 일입니다. 에러가 발생하면 다시 숫자를 입력받도록 코드를 수정하고 실행해 보겠습니다.

파이썬 코드
while True:
    try:
        num_1 = int(input('첫 번째 숫자: '))
        num_2 = int(input('두 번째 숫자: '))
        result = num_1 / num_2
        print('{} / {} = {}'.format(num_1, num_2, result))
        break
    except ValueError as error:
        print('입력하신 값은 숫자가 아닙니다. 다시 입력해주세요.')
콘솔 출력
 번째 숫자: one
입력하신 값은 숫자가 아닙니다. 다시 입력해주세요.
 번째 숫자: 4
 번째 숫자: 2
4 / 2 = 2.0

이번에는 ValueError 에러가 아닌 다른 에러에 대해서 살펴보도록 하겠습니다. 초보 프로그래머들이 가장 많이 하는 실수 중 하나는 숫자를 0으로 나누는 것입니다. 산수나 수학에서는 숫자를 0으로 나누면 0 되지만, 프로그래밍에서 숫자를 0으로 나누면 ZeroDivisionError 라는 에러가 발생하게 됩니다. 0을 입력하여 에러를 발생시켜 보겠습니다.

콘솔 출력
 번째 숫자: 4
 번째 숫자: 0
Traceback (most recent call last):
  File "/Users/curtis/Dev/work/function.py", line 5, in <module>
    result = num_1 / num_2
ZeroDivisionError: division by zero

except 키워드는 여러번 사용 가능하여 여러개의 에러를 예외처리 할수가 있습니다. ValueError 에러를 처리하는 코드와 동일하게 ZeroDivisionError 에러를 처리하는 코드를 추가하고 실행해 보겠습니다.

파이썬 코드
while True:
    try:
        num_1 = int(input('첫 번째 숫자: '))
        num_2 = int(input('두 번째 숫자: '))
        result = num_1 / num_2
        print('{} / {} = {}'.format(num_1, num_2, result))
        break
    except ValueError as error:
        print('입력하신 값은 숫자가 아닙니다. 다시 입력해주세요.')
    except ZeroDivisionError as error:
        print('0으로 나눌 수 없습니다. 다시 입력해주세요.')
콘솔 출력
 번째 숫자: one
입력하신 값은 숫자가 아닙니다. 다시 입력해주세요.
 번째 숫자: 4
 번째 숫자: 0
0으로 나눌 없습니다. 다시 입력해주세요.
 번째 숫자: 4
 번째 숫자: 2
4 / 2 = 2.0

2개의 에러가 잘 예외처리 되는 것을 확인하였습니다.

ValueErrorZeroDivisionError는 파이썬 내장 예외 오브젝트의 이름이며, 이 내장예외 클래스에 대해서는 아래의 파이썬 공식 문서에서 확인 하실 수가 있습니다.

참조링크: 파이썬 내장 예외

위의 코드와 같이 발생할 모든 에러를 예상하고 예외 처리를 하기는 어렵습니다. 그래서 예상되는 에러 이외에 다른 모든 에러에 대해서 예외 처리를 해야 하는 경우가 있습니다.

또 다른 NameError를 발생시키는 코드를 추가하고 실행보겠습니다.

파이썬 코드
while True:
    try:
        num_1 = int(input('첫 번째 숫자: '))
        num_2 = int(input('두 번째 숫자: '))
        result = num_1 / num_2
        print('{} / {} = {}'.format(num_1, num_2, result))
        print(bad_variable)  # 존재하지 않는 변수를 호출
        break
    except ValueError as error:
        print('입력하신 값은 숫자가 아닙니다. 다시 입력해주세요.')
    except ZeroDivisionError as error:
        print('0으로 나눌 수 없습니다. 다시 입력해주세요.')
콘솔 출력
 번째 숫자: 4
 번째 숫자: 2
4 / 2 = 2.0
Traceback (most recent call last):
  File "/Users/curtis/Dev/work/function.py", line 7, in <module>
    print(bad_variable)
NameError: name 'bad_variable' is not defined

이런 경우의 문제를 해결하기 위해 특정 에러 이름을 지정 안 하고 모든 에러에 대해서 예외 처리를 하는 코드를 추가해 보겠습니다. 이때는 예외 오브젝트의 베이스 오브젝트인 Exception 오브젝트를 사용하시면 됩니다.

파이썬 코드
while True:
    try:  # 존재하지 않는 변수를 호출
        num_1 = int(input('첫 번째 숫자: '))
        num_2 = int(input('두 번째 숫자: '))
        result = num_1 / num_2
        print('{} / {} = {}'.format(num_1, num_2, result))
        print(bad_variable)
        break
    except ValueError as error:
        print('입력하신 값은 숫자가 아닙니다. 다시 입력해주세요.')
    except ZeroDivisionError as error:
        print('0으로 나눌 수 없습니다. 다시 입력해주세요.')
    except Exception:
        print('또 다른 에러가 발생했습니다. 🥲')
        break
콘솔 출력
 번째 숫자: 4
 번째 숫자: 2
4 / 2 = 2.0
 다른 에러가 발생했습니다. 🥲

Exception 오브젝트를 사용하실 때는 가장 아래 부분에서 사용하셔야 합니다. 그렇지 않으면 모든 에러가 이 부분에 캐치되기 때문입니다. 가장 위로 위치를 변경하고 실행해 보겠습니다.

파이썬 코드
while True:
    try:  # 존재하지 않는 변수를 호출
        num_1 = int(input('첫 번째 숫자: '))
        num_2 = int(input('두 번째 숫자: '))
        result = num_1 / num_2
        print('{} / {} = {}'.format(num_1, num_2, result))
        print(bad_variable)
        break
    except Exception:
        print('또 다른 에러가 발생했습니다. 🥲')
        break
    except ValueError as error:
        print('입력하신 값은 숫자가 아닙니다. 다시 입력해주세요.')
    except ZeroDivisionError as error:
        print('0으로 나눌 수 없습니다. 다시 입력해주세요.')
콘솔 출력
 번째 숫자: one   
 다른 에러가 발생했습니다. 🥲

첫 번째 숫자에 문자열 "one"을 입력하였는데, ValueError 블록이 아닌 Exception블록이 실행된 것을 볼 수 있습니다.

이번에는 else 키워드를 사용해 보겠습니다. else 블록은 하기 코드와 같이 try 블록과 except 블록 다음에 위치하게 되면, else의 보디 블록에는 어떤 에러도 발생하지 않았을 경우에 실행하고자 하는 코드를 넣으시면 됩니다.

파이썬 코드
while True:
    try:  # 존재하지 않는 변수를 호출
        num_1 = int(input('첫 번째 숫자: '))
        num_2 = int(input('두 번째 숫자: '))
        result = num_1 / num_2
        print('{} / {} = {}'.format(num_1, num_2, result))
        break
    except ValueError as error:
        print('입력하신 값은 숫자가 아닙니다. 다시 입력해주세요.')
    except ZeroDivisionError as error:
        print('0으로 나눌 수 없습니다. 다시 입력해주세요.')
    except Exception:
        print('또 다른 에러가 발생했습니다. 🥲')
    else:
        print('어떤 에러도 발생하지 발생하지 않았습니다.')
콘솔 출력
 번째 숫자: 4
 번째 숫자: 2
4 / 2 = 2.0

어떤 에러도 발생하지 않았는데도 else 블록이 실행되지 않았습니다. 그 이유는 에러가 발생 안 하면 try 블록의 break 키워드가 while 루프를 종료시켜 버리기 때문입니다. break 키워드를 else 블록 안으로 옮기고 다시 실행해 보겠습니다.

파이썬 코드
while True:
    try:  # 존재하지 않는 변수를 호출
        num_1 = int(input('첫 번째 숫자: '))
        num_2 = int(input('두 번째 숫자: '))
        result = num_1 / num_2
        print('{} / {} = {}'.format(num_1, num_2, result))
    except ValueError as error:
        print('입력하신 값은 숫자가 아닙니다. 다시 입력해주세요.')
    except ZeroDivisionError as error:
        print('0으로 나눌 수 없습니다. 다시 입력해주세요.')
    except Exception:
        print('또 다른 에러가 발생했습니다. 🥲')
    else:
        print('어떤 에러도 발생하지 발생하지 않았습니다.')
        break
파이썬 코드
첫 번째 숫자: 4
두 번째 숫자: 2
4 / 2 = 2.0
어떤 에러도 발생하지 발생하지 않았습니다.

이번에는 else 블록이 잘 실행되었습니다.

마지막으로 finally 키워드에 대해서 알아보겠습니다. finally 블록안의 코드는 에러가 발생하던 안 하던 마지막에 무조건 실행됩니다. attempt 라는 변수를 while 루프 밖에 정의하고, while 루프가 실행되는 횟수를 카운트 하도록 하겠습니다.

파이썬 코드
attempt = 0
 
while True:
    try:  # 존재하지 않는 변수를 호출
        num_1 = int(input('첫 번째 숫자: '))
        num_2 = int(input('두 번째 숫자: '))
        result = num_1 / num_2
        print('{} / {} = {}'.format(num_1, num_2, result))
    except ValueError as error:
        print('입력하신 값은 숫자가 아닙니다. 다시 입력해주세요.')
    except ZeroDivisionError as error:
        print('0으로 나눌 수 없습니다. 다시 입력해주세요.')
    except Exception:
        print('또 다른 에러가 발생했습니다. 🥲')
    else:
        print('어떤 에러도 발생하지 발생하지 않았습니다.')
        break
    finally:
        attempt += 1
        print('실행 횟수: {}'.format(attempt))
콘솔 출력
 번째 숫자: one
입력하신 값은 숫자가 아닙니다. 다시 입력해주세요.
실행 횟수: 1
 번째 숫자: 4
 번째 숫자: 0
0으로 나눌 없습니다. 다시 입력해주세요.
실행 횟수: 2
 번째 숫자: 4
 번째 숫자: 2
4 / 2 = 2.0
어떤 에러도 발생하지 발생하지 않았습니다.
실행 횟수: 3

finally 블록 안의 코드가 매 번 실행되는 것을 확인하였습니다.

이번 강좌는 여기서 마치고 다음 강좌에서는 모듈과 페키지에 대해서 공부하도록 하겠습니다. 수고하셨습니다~! 👍