04. 에러 & 예외 처리

Error / Exception Handling

  • 에러(Error)

  • 예외 처리(Exception Handling)

파이썬 문서

4.1 디버깅

“코드의 상태를 신중하게 출력해가며 심사숙고하는 것보다 효과적인 디버깅 도구는 없습니다.”

                    - 브라이언 커니핸, Unix for Beginners.
  • print문 활용

    • 특정 함수 결과, 반복 / 조건 결과 등 나눠서 생각

  • 개발 환경(text editor, IDE) 등에서 제공하는 기능 활용

    • breakpoint, 변수 조회 등

  • python tutor 활용 (단순 파이썬 코드인 경우)

  • 뇌컴파일, 눈디버깅

코드를 작성하다가 …

  • 에러 메세지가 발생하는 경우

    • 해당하는 위치를 찾아 메세지를 해결

  • 로직 에러가 발생하는 경우

    • 명시적인 에러 메세지 없이 예상과 다른 결과가 나온 경우

      • 정상적으로 동작했던 코드 이후 작성된 코드를 생각해봄

      • 전체 코드를 살펴봄

      • 휴식을 가져봄

      • 누군가에게 설명해봄

4.2 에러

발생할 수 있는 에러(Error)의 종류를 확인해봅시다.

문법 에러(Syntax Error)

문법 에러가 있는 프로그램은 실행되지 않습니다.

  • 에러 발생 시 SyntaxError라는 키워드와 함께, 에러의 상세 내용을 보여줍니다.

  • 파일이름줄번호, ^ 문자를 통해 파이썬이 코드를 읽어 들일 때(parser) 문제가 발생한 위치를 표현합니다.

  • parser 는 줄에서 에러가 감지된 가장 앞의 위치를 가리키는 캐럿(caret)기호(^)를 표시합니다.

# 조건문을 통해 문법 에러를 발생시켜봅시다.

# 아래 코드는 else문 뒤에 콜론이 누락되어 있습니다.
# 코드를 실행시켜보고 invalid syntax 오류를 확인해보세요.

if True:
    print('참')
else
    print('거짓')
  File "C:\Users\User\AppData\Local\Temp/ipykernel_5624/4061262576.py", line 8
    else
        ^
SyntaxError: invalid syntax
# print문을 통해 다른 오류를 발생시켜봅시다.

# 아래 코드는 닫는 따옴표가 누락되어 있습니다.
# 코드를 실행시켜보고 EOL 오류(따옴표 오류)를 확인해봅시다.

print('hi)
  File "C:\Users\User\AppData\Local\Temp/ipykernel_5624/3802404740.py", line 6
    print('hi)
              ^
SyntaxError: EOL while scanning string literal
# EOF 에러(괄호 닫기 오류)도 보게됩니다.

print('hi'
  File "C:\Users\User\AppData\Local\Temp/ipykernel_5624/3660871560.py", line 3
    print('hi'
              ^
SyntaxError: unexpected EOF while parsing
# 정확한 위치를 지정하지 않을 수도 있으므로 지정된 위치 전후를 모두 확인해야합니다.

# 아래 코드의 조건문에는 콜론이 누락되어 있습니다.
# 코드를 실행시켜보고 문법 오류를 확인해보세요.

if True print('참')
  File "C:\Users\User\AppData\Local\Temp/ipykernel_5624/197234176.py", line 6
    if True print('참')
            ^
SyntaxError: invalid syntax

4.3 예외

실행 도중 예상하지 못한 상황(exception)을 맞이하면, 프로그램 실행을 멈춥니다.

  • 문법적으로는 옳지만, 실행시 발생하는 에러입니다.

  • 즉, 실행 중에 감지되는 에러들을 예외(exeption)라고 부릅니다.

  • 예외는 여러 타입 (type)으로 나타나고, 타입이 메세지의 일부로 출력됩니다.

    • NameError, TypeError 등은 발생한 예외 타입의 종류(이름)

  • 아래 제시된 모든 에러는 Exception을 상속받아 이뤄집니다.

  • 사용자 정의 예외를 만들어 관리할 수 있습니다.

4.3.1 ZeroDivisionError

파이썬에서는 어떤 수를 0으로 나누게 되면 에러가 발생합니다

# ZeroDivisionError를 확인해봅시다.
# 어떤 수를 0으로 나누는 코드를 작성해보고 오류를 확인해보세요.

1 / 0
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_5624/1749619089.py in <module>
      2 # 어떤 수를 0으로 나누는 코드를 작성해보고 오류를 확인해보세요.
      3 
----> 4 1 / 0

ZeroDivisionError: division by zero

4.3.2 NameError

지역 혹은 전역 이름 공간(namespace) 내에서 유효하지 않는 이름은 사용할 수 없습니다. 즉, 어느 곳에서도 정의되지 않은 변수를 호출 하였을 경우 에러가 발생합니다.

# NameError를 확인해봅시다. 
# abc라는 변수를 print로 출력해보고 오류를 확인해보세요.

print(abc)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_5624/2011198854.py in <module>
      2 # abc라는 변수를 print로 출력해보고 오류를 확인해보세요.
      3 
----> 4 print(abc)

NameError: name 'abc' is not defined

4.3.3 TypeError

자료형이 올바르지 못한 경우에도 에러가 발생합니다.

  • 타입 불일치

  • 매개변수 타입 불일치

  • 매개변수 누락

  • 매개변수 개수 초과

(1) 타입 불일치

# TypeError를 확인해봅시다.
# 숫자 1과 문자 1을 더하는 코드를 작성해보고 오류를 확인해보세요.

1 + '1'
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_5624/660587461.py in <module>
      2 # 숫자 1과 문자 1을 더하는 코드를 작성해보고 오류를 확인해보세요.
      3 
----> 4 1 + '1'

TypeError: unsupported operand type(s) for +: 'int' and 'str'

(2) 매개변수 타입 불일치

# 함수 호출과정에서도 TypeError 오류가 발생할 수 있습니다.
# round 함수는 어떤 수를 반올림해주는 내장 함수입니다.
# round 함수에 숫자가 아닌 문자를 넣어보고 발생하는 오류를 확인해봅시다.

round('3.6')
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_5624/4087423749.py in <module>
      3 # round 함수에 숫자가 아닌 문자를 넣어보고 발생하는 오류를 확인해봅시다.
      4 
----> 5 round('3.6')

TypeError: type str doesn't define __round__ method

(3) 필수 매개변수가 누락된 경우

# 함수호출 과정에서 다양한 오류를 확인할 수 있습니다. 
# (1) 필수 매개변수가 누락된 경우를 살펴봅시다.

# 내장 random 모듈을 불러오세요.
# random.sample() 함수는 2개의 매개변수를 받도록 정의되어 있습니다.
# random.sample() 함수에 숫자 3개가 담긴 리스트만 넣고 호출해보세요.
# 그리고 매개변수가 누락되어 발생하는 오류를 확인해보세요.

import random
random.sample([1, 2, 3])
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_5624/3720091620.py in <module>
      8 
      9 import random
---> 10 random.sample([1, 2, 3])

TypeError: sample() missing 1 required positional argument: 'k'

(4) 매개변수 개수가 초과해서 들어온 경우

# 함수호출 과정에서 다양한 오류를 확인할 수 있습니다. 
# (2) 매개변수 개수가 초과해서 들어온 경우를 살펴봅시다.

# random.choice() 함수는 하나의 매개변수만 받도록 정의되어 있습니다.
# 숫자 3개가 담긴 리스트와, 숫자 6을 넣고 호출해보세요.
# 그리고 매개변수가 초과되어 발생하는 오류를 확인해보세요.
# ====
random.choice([1, 2, 3], 6)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_5624/1181987922.py in <module>
      6 # 그리고 매개변수가 초과되어 발생하는 오류를 확인해보세요.
      7 # ====
----> 8 random.choice([1, 2, 3], 6)

TypeError: choice() takes 2 positional arguments but 3 were given

4.3.4 ValueError

  • 자료형은 올바르나 값이 적절하지 않은 경우

  • 존재하지 않는 값을 찾고자 할 경우

# ValueError를 확인해봅시다.

# int()는 정수가 아닌 값을 받았을 경우 에러가 발생합니다.
# int() 안에 문자 3.5를 넣고 호출한 뒤 발생하는 오류를 확인해보세요.

int('3.5')
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_5624/110327471.py in <module>
      4 # int() 안에 문자 3.5를 넣고 호출한 뒤 발생하는 오류를 확인해보세요.
      5 
----> 6 int('3.5')

ValueError: invalid literal for int() with base 10: '3.5'
# ValueError를 확인해봅시다.

# index()는 리스트에서 찾고자 하는 값의 인덱스를 반환합니다.
# numbers 리스트에 없는 값인 3을 찾게 되면 에러가 발생합니다.
# 아래 코드를 실행시킨 뒤 발생하는 오류를 확인해보세요.

numbers = [1, 2]
numbers.index(3)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_5624/2635018620.py in <module>
      6 
      7 numbers = [1, 2]
----> 8 numbers.index(3)

ValueError: 3 is not in list

4.3.5 IndexError

인덱스가 존재하지 않거나 범위를 벗어나는 경우

# IndexError를 확인해봅시다.

# 비어있는 리스트는 어떤 인덱스 값으로든 접근할 수 없습니다.
# 비어있는 empty_list를 -1 인덱스로 접근했을 때 발생하는 오류를 확인해보세요.

empty_list = []
empty_list[-1]
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_5624/1949616608.py in <module>
      5 
      6 empty_list = []
----> 7 empty_list[-1]

IndexError: list index out of range

4.3.6 KeyError

딕셔너리에서 Key가 없는 경우

# KeyError를 확인해봅시다. 

# 딕셔너리는 존재하지 않는 Key로 접근하게 되면 오류가 발생합니다.
# 아래 songs라는 딕셔너리에는 'sia'라는 Key만 존재하며,
# 'queen'이라는 키는 존재하지 않습니다.
# 코드를 실행시켜보고 발생하는 오류를 확인해보세요.

songs = {'sia': 'candy cane lane'}
songs['queen']
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_5624/3358014412.py in <module>
      7 
      8 songs = {'sia': 'candy cane lane'}
----> 9 songs['queen']

KeyError: 'queen'

4.3.7 ModuleNotFoundError

존재하지 않는 모듈을 import 하는 경우

# ModuleNotFoundError를 확인해봅시다.

# 파이썬에 존재하지 않는 모듈인 "reque"라는 이름의 모듈을 불러와봅시다(import).
# 그리고 발생하는 오류를 확인해보세요.

import reque
---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_5624/889465546.py in <module>
      4 # 그리고 발생하는 오류를 확인해보세요.
      5 
----> 6 import reque

ModuleNotFoundError: No module named 'reque'

4.3.8 ImportError

모듈을 찾았으나 가져오는 과정에서 실패하는 경우 (존재하지 않는 클래스/함수 호출)

# ImportError를 확인해봅시다.

# 파이썬 내장 random 모듈은 존재하나 그 안에 "sampl"이라는 함수는 존재하지 않습니다.
# random 모듈을 불러와서 "sampl"이라는 함수를 불러와보고, 발생하는 오류를 확인해보세요.

from random import sampl
---------------------------------------------------------------------------
ImportError                               Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_5624/1029549712.py in <module>
      4 # random 모듈을 불러와서 "sampl"이라는 함수를 불러와보고, 발생하는 오류를 확인해보세요.
      5 
----> 6 from random import sampl

ImportError: cannot import name 'sampl' from 'random' (c:\users\user\appdata\local\programs\python\python39\lib\random.py)

4.3.9 KeyboardInterrupt

주피터 노트북에서는 정지 버튼이지만, 실제로 우리가 돌릴 때는 ctrl+c를 통해 종료하였을 때 발생합니다.

# KeyboardInterrupt를 확인해봅시다.

# 무한 반복되는 while문을 실행시켜보고, 정지시켜보세요.
# 그리고 발생하는 오류를 확인해보세요.

while True:
    pass
---------------------------------------------------------------------------
KeyboardInterrupt                         Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_5624/2943268627.py in <module>
      5 
      6 while True:
----> 7     pass

KeyboardInterrupt: 

4.3.10 Indentation Error

Indentation이 적절하지 않은 경우

for i in range(3):
print(i)
  File "C:\Users\User\AppData\Local\Temp/ipykernel_5624/3296739069.py", line 2
    print(i)
    ^
IndentationError: expected an indented block
for i in range(3):
    print(i)
        print(i)
  File "C:\Users\User\AppData\Local\Temp/ipykernel_5624/955869009.py", line 3
    print(i)
    ^
IndentationError: unexpected indent

4.4 예외 처리

예외 처리

  • try : 코드를 실행함

  • except : try 문에서 예외가 발생 시 실행함

  • else : try 문에서 예외가 발생하지 않으면 실행함

  • finally : 예외 발생 여부와 관계없이 항상 실행함

try:
    면접장 입장
    자기소개
    질의응답
    PT면접
except 입장하다 넘어지면:
    울면서 집가기
except 압박면접:
    침착하고 웃으며 답변
else:
    속으로 만세삼창
finally:
    예의바르게 인사하고 나와서 문닫기

4.4.1 try & except

try 문(statement)와 except 절(clause)을 이용하여 예외 처리(Exception Handling)를 할 수 있습니다.


기초 문법

try:
    <코드 블럭 1>
except (예외):
    <코드 블럭 2>
  • try 아래의 코드블락(code block)이 실행됩니다.

  • 예외가 발생되지 않으면, except없이 실행이 종료 됩니다.

  • 예외가 발생하면, 남은 부분을 수행하지 않고, except가 실행됩니다.

# 사용자로부터 값을 받아 정수로 변환하여 출력해봅시다.

# input() 함수를 이용하여 사용자로부터 입력을 받은 뒤
# 해당 값을 정수로 변환하여 출력해보세요.

int(input())
 ??
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_5624/907681871.py in <module>
      4 # 해당 값을 정수로 변환하여 출력해보세요.
      5 
----> 6 int(input())

ValueError: invalid literal for int() with base 10: '??'
# 사용자가 문자열을 넣어 해당 오류(ValueError)가 발생하면, 숫자를 입력하라고 출력해봅시다.
# 위에서 배운 try-except 구문을 활용해보세요.

try:
    print(int(input()))
except:
    print('숫자를 입력하세요')
 ??
숫자를 입력하세요

복수의 예외 처리

하나 이상의 예외를 모두 처리할 수 있습니다.

괄호가 있는 튜플로 여러 개의 예외를 지정할 수 있습니다.


활용법

try:
    <코드 블럭 1>
except (예외1, 예외2):
    <코드 블럭 2>


try:
    <코드 블럭 1>
except 예외1:
    <코드 블럭 2>
except 예외2:
    <코드 블럭 3>
# 숫자 100을 사용자가 입력한 값으로 나눈 후 출력하는 코드를 작성해봅시다.

# input() 함수를 이용하여 사용자로부터 입력을 받으세요.
# 해당 값을 정수로 변환한 뒤, 숫자 100을 입력받은 값으로 나누는 코드를 작성해보세요.

number = input('100을 무슨 수로 나눌까?')
print(100 / int(number))
100을 무슨 수로 나눌까? 1
100.0
# 문자열일때와 0일때의 경우를 모두 처리를 해봅시다.

# 어떤 값을 숫자가 아닌 값으로 나눌 때 발생하는 에러는 ValueError입니다.
# 어떤 값을 0으로 나눌 때 발생하는 에러는 ZeroDivisionError입니다.
# try-except 구문을 활용하여 위의 두 오류를 처리해보세요.

try: 
    number = input('100을 무슨 수로 나눌까?')
    print(100 / int(number))
except (ValueError, ZeroDivisionError):
    print('나눌 수 있는 걸 입력해줘')
100을 무슨 수로 나눌까? 0
나눌 수 있는 걸 입력해줘
# 각각 다른 오류를 출력할 수 있습니다.
# 여러 개의 except 구문을 활용해보세요.
# (ValueError, ZeroDivisionError)

try: 
    number = input('100을 무슨 수로 나눌까?')
    print(100 / int(number))
except ValueError:
    print('문자 말고 정수를 넣어주세요.')
except ZeroDivisionError:
    print('0은 안되지')
100을 무슨 수로 나눌까? ㅁ
문자 말고 정수를 넣어주세요.
  • 여기서 중요한 내용은 에러가 순차적으로 수행됨으로, 가장 작은 범주부터 시작해야 합니다.

# Exception은 가장 큰 범주의 에러로써 모든 에러를 처리할 수 있습니다.
# 따라서 아래 코드는 숫자가 아닌 값을 넣었을 때 순차적으로 먼저 적힌 Exception 에러가 발생합니다.
# 코드를 실행하고 결과를 확인해보세요.

try:
    num = input('값을 입력하시오: ')
    100/int(num)
except Exception: # Exception 은 가장 큰 범주
    print('모르겠지만 에러야')
except ValueError:
    print('숫자를 넣어')
값을 입력하시오:  a
모르겠지만 에러야

4.4.2 else

  • 에러가 발생하지 않는 경우 수행되는 문장은 else를 이용합니다.

  • 모든 except 절 뒤에와야 합니다.

  • try 절이 예외를 일으키지 않을 때 실행되어야만 하는 코드에 적절합니다.


활용법

try:
    <코드 블럭 1>
except 예외:
    <코드 블럭 2>
else:
    <코드 블럭 3>
# else를 사용해봅시다.

# try 구문에서 numbers라는 이름의 리스트에 숫자 3개를 저장하세요.
# 그리고 존재하지 않는 인덱스의 값을 가져와서 number 변수에 저장하세요.
# (이 때, 존재하지 않는 인덱스를 참고하는 경우 IndexError가 발생하게 됩니다.)
# except 구문에서 IndexError가 발생할 경우 '오류 발생'이라는 메세지를 출력하세요.
# 마지막으로 else 구문을 활용하여 number * 100을 출력해보세요.
try:
    numbers = [1, 2, 3]
    number = numbers[2]
except IndexError:
    print('오류 발생')
else:
    print(number * 100)
300
try:
    numbers = [1, 2, 3]
    number = numbers[100]
except IndexError:
    print('오류 발생')
else:
    print(number * 100)
오류 발생

4.4.3 finally

  • 반드시 수행해야하는 문장은 finally를 활용합니다.

  • 즉, 모든 상황에 실행되어야만 하는 코드를 정의하는데 활용합니다.

  • 예외의 발생 여부과 관계없이 try 문을 떠날 때 항상 실행합니다.


활용법

try:
    <코드 블럭 1>
except 예외:
    <코드 블럭 2>
finally:
    <코드 블럭 3>
# finally를 사용해봅시다.
# 아래 코드에서 finally 구문을 활용하여 '성적 파일을 종료합니다'라는 메세지를 출력해보세요.

try:
    print('성적 파일을 읽어옵니다.')
    data = {'python': 'A+'}
    data['java']
except KeyError as err:
    print(f'{err}는 딕셔너리에 없는 키입니다.')
finally:
    print('성적 파일을 종료합니다')
성적 파일을 읽어옵니다.
'java'는 딕셔너리에 없는 키입니다.
성적 파일을 종료합니다

4.4.4 에러 메시지 처리 as

as 키워드를 활용하여 에러 메시지를 보여줄 수도 있습니다.


활용법

try:
    <코드 블럭 1>
except 예외 as err:
    <코드 블럭 2>
# except 구문에서 발생하는 에러 메세지를 코드 블럭에 넘겨줄 수도 있습니다.

# 아래 코드에서 as를 활용하여 에러 메세지를 그 아래 코드 블럭에 넘겨보세요.
# 그리고 as로 명명한 에러 메세지를 print를 이용하여 출력해보세요.

try:
    empty_list = []
    print(empty_list[-1])
except IndexError:
    # 여기에 코드를 작성하세요
    print('인덱스 에러가 발생')
인덱스 에러가 발생
try:
    empty_list = []
    print(empty_list[-1])
except IndexError as err:
    print(f'{err}, 오류가 발생했습니다.')
list index out of range, 오류가 발생했습니다.

4.5 예외 발생 시키기

4.5.1 raise

raise를 통해 예외를 강제로 발생(Exception Raising)시킬 수 있습니다.


활용법

raise <에러>('메시지')
# raise를 사용해봅시다.

# raise만 작성한 뒤 실행시켜봅시다.
# 아래 코드를 실행시켜보고 결과를 확인하세요.

raise
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_5624/308333447.py in <module>
      4 # 아래 코드를 실행시켜보고 결과를 확인하세요.
      5 
----> 6 raise

RuntimeError: No active exception to reraise
# 이번에는 ValueError() 오류를 raise해봅시다.
# 아래 코드를 실행시켜보고 결과를 확인하세요.

raise ValueError('hi')
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_5624/1307088514.py in <module>
      2 # 아래 코드를 실행시켜보고 결과를 확인하세요.
      3 
----> 4 raise ValueError('hi')

ValueError: hi

[연습] raise 예외 발생시키기

리스트를 받아 평균을 반환하는 def avg(scores)를 작성하세요.


  • scores의 길이가 0인 경우 Exception과 메시지를 발생시키세요.

    • 예) Exception: 학생이 없습니다.

  • 정상적인 경우에는 결과를 return합니다.

# 아래에 코드를 작성하세요.

def get_avg(scores):
    if scores:
        return sum(scores)/len(scores)
    else:
        raise ValueError('학생이 없습니다.')     
# 해당 코드를 통해 올바른 결과가 나오는지 확인하세요.
get_avg([])
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_5624/2485888008.py in <module>
      1 # 해당 코드를 통해 올바른 결과가 나오는지 확인하세요.
----> 2 get_avg([])

~\AppData\Local\Temp/ipykernel_5624/499962949.py in get_avg(scores)
      5         return sum(scores)/len(scores)
      6     else:
----> 7         raise ValueError('학생이 없습니다.')

ValueError: 학생이 없습니다.

4.5.2 assert

assert 문은 예외를 발생시키는 다른 방법입니다.

보통 상태를 검증하는데 사용되며 무조건 AssertionError가 발생합니다.


활용법

assert Boolean expression, error message

assert len([1, 2]) == 1, '길이가 1이 아닙니다.'

위의 검증식이 거짓일 경우를 발생합니다.

일반적으로 디버깅용도로 사용됩니다. 파이썬 문서

$ python code.py
Traceback (most recent call last):
  File "code.py", line 1, in <module>
    assert len([1, 2]) == 1, '길이가 1이 아닙니다.'
AssertionError: 길이가 1이 아닙니다.

$ python -O code.py