파이썬 예외처리 쉽게 이해하기

  • 파이썬

파이썬으로 코드를 짜다보면 반드시 예외, 즉 에러가 발생할 가능성을 염두에 두고 작업을 해야 한다. 초특급 개발자가 완벽한 코드를 짜더라도, 소위 말하는 코드의 “무결성”을 갖췄다고 자신하더라도 하드웨어 상에서 뜬금없는 오류가 생길 수도 있기 때문에.

게다가 나 같은 초보는 한 줄 건너 한 줄 에러…… ㅜㅜㅜㅜ

보통 코딩을 할 VS Code, 파이참과 같은 IDE(통합개발환경)에서 개발을 하는데, 이러면 어느정도 문법을 자동으로 교정해주기 때문에 크게 걱정하지 않아도 된다. 그런데 문법적으로는 예외가 없더라도 정작 코드를 실행할 때 (런타임) 프로세스에서 발생할 수 있는 예외들도 있기 때문에 예외 처리는 정말 중요하다.

본 포스팅 목차는 다음과 같다

  • 파이썬 예외(에러)의 종류
  • 예외 처리 전략 (EAFP vs LBYL)
  • 파이썬 예외처리의 기본 try-except-else-finally
  • raise 키워드로 직접 예외 발생시키기

파이썬 예외(에러)의 종류

우선은 파이썬에서 발생하는 예외, 에러의 종류에 대해 알아보자.

SyntaxError : 잘못된 문법

보통 코딩을 할 VS Code, 파이참과 같은 IDE(통합개발환경)에서 개발을 하는데, 이러면 어느정도 문법을 자동으로 교정해주기 때문에 크게 걱정하지 않아도 된다. 그래도 뭐 콜론 같은 거 빼먹는 경우가 종종 있어서 막상 실행하면 SyntaxError: unvalid syntax 라는 에러 메시지를 흔히 마주할 수 있다.

NameError : 참조변수 없음

이건 예를 들면 코드에서 변수 a 를 출력하도록 했는데, 코드 상에서 a 를 선언한 적이 없다면 발생하는 에러다. NameError: name 'a' is not defined 라고 뜰 거다. 정의되지 않았다는 뜻이 되겠다.

ZeroDivisionError : 0으로 나눌 수 없음

파이썬에서 print(10/0)이라고 코딩을 해버리면, 10 나누기 0 값을 출력한다는 의미인데, 이건 문법적으로는 에러가 없지만 막상 실행하면 런타임 시에 ZeroDivisionError: division by zero 라는 오류 메시지가 뜬다. 엑셀에서도 숫자 좀 다뤄본 경험이 있다면 #DIV/0!를 본 적 있을 거다. 같은 거다.

IndexError : 인덱스 범위 벗어남

예를 들어 a = [10, 20, 30] 이라는 원소 3개를 가진 리스트에서 print(a[4])와 같이 4번째 원소를 호출할 경우 발생하는 에러다. IndexError: list index out of range 라고 범위를 벗어났다는 메시지가 뜰 거다.

ValueError : 참조 값이 없음

예를 들어 a = [10, 20, 30] 이라는 원소 3개를 가진 리스트에서 a.remove(40)과 같이 40이라는 값을 가진 원소를 지우려고 하면 발생하는 애초에 그런 값이 없기 때문에 에러가 발생한다. ValueError: list.remove(x): x not in list 라고 그런 값이 리스트에 포함되어 있지 않다는 메시지가 뜬다. a.index(40)라고 써도 마찬가지겠지.

KeyError : 키 없음 에러 (주로 딕셔너리 사용시)

mydict = {'Kim': 1, 'Lee': 2} 라는 딕셔너리가 있을 때 print(mydict['Park'])라고 출력하면 발생하는 에러다. KeyError: 'Park' 라고 메시지가 뜬다.

이럴 땐 get 메소드를 활용하면 좋다. print(mydict.get('Park'))라고 써보면 None이 출력된다. 키가 없을 경우 None값이 반환되기도 하고, 그 뒤에 키가 없을 때 반환할 값을 지정해놓아도 된다. 이렇게 print(mydict.get('Park', 'unknown'))

AttributeError : 모듈, 클래스의 잘못된 속성 사용함

예를 들어 import time으로 time 모듈을 활용하고자 할 때 time.sleep()이라고 하면 에러가 안 나지만, time.month()라고 엉뚱한 함수를 사용하면 에러가 발생한다. AttributeError: module 'time' has no attribute 'month'라고, time 모듈은 month라는 속성이 없다고 친절하게 알려준다. (물론 그 전에 IDE를 사용하면 에디터 상에서 알아서 내가 사용할 수 있는 속성들을 안내해주지만…)

FileNotFoundError : 파일 못 찾음

예를 들어 어떤 텍스트 파일을 읽기 위해 f= open('ex.txt', 'r')이라고 쳤을 때, 해당 경로에 파일이 없다면 FileNotFoundError: [Errno 2] No such file or directory: 'ex.txt' 라는 메시지가 뜬다. 그런 파일이나 디렉토리(폴더)가 없다는 뜻이다.

TypeError : 타입 안 맞음

a = [1,2] 라고 리스트를 만들어 놓고, b = “Hello”라고 문자열을 만든 뒤 둘을 더하기로 연결해보자. print(a+b)라고 하면 TypeError: can only concatenate list (not "str") to list 라고 해서 리스트는 (문자열이 아닌) 리스트와 결합할 수 있습니다~ 라고 뜬다.


예외 처리 전략 (EAFP vs LBYL)

지금까지 살펴본 것들만 해도 종류가 참 많은데 또 어디서 어떤 에러가 발생할지 모른다. 그래서 예외를 처리하기 위해 크게 두가지 전략을 사용하곤 한다.

EAFP

이건 “It’s Easier to Ask Forgiveness than Permission”, 미리 허락을 구하는 것보다는 나중에 용서를 구하는 게 쉽다는 뜻이다. 그래서 일단 항상 예외가 발생하지 않을 것으로 가정하고 나름대로의 완벽한 코딩을 한다. 그리고 막상 실행을 했는데 런타임 에러가 발생한다면 그때 예외처리 코딩을 하는 게 좋다는 철학이다. 그래서 일단 수행(try)시키고, 만약 에러가 발생하면 그때 처리(except)한다는 스타일로 코딩한다.

LBYL

이건 “Look Before You Leap”, 누울 자리를 보고 다리를 뻗으라는 뜻. 어떤 코드를 실행하기 전에 에러가 발생할 수 있는 조건을 미리 다 따져보고 그걸 어떻게 처리할지 조건문(if) 스타일로 코딩한다.

참고로 파이썬에서는 LBYL보다 EAFP를 권장한다. (PEP-0463)

파이썬 예외처리의 기본 try-except-else-finally

예외 처리는 이 네 개만 알면 된다.

  • try : 에러 발생 가능성이 있는 코드 실행
  • except : 에러 발생 시 (생략 가능, 여러개 사용 가능)
  • else : 에러가 발생하지 않았을 경우 실행 (생략 가능)
  • finally : 항상 실행 (생략 가능)

그리고 위에서 설명한 에러 종류들을 직접 명시해서 except ValueError: , except NameError: 이런 식으로 써도 되지만, 어떤 에러가 발생할지 모르겠을 때는 최종적으로 except Exception: 이라고 해서 이런저런 에러를 무조건 다 잡아버릴 수도 있다. except Exception as e: 라고 쓰고 print(e)로 출력하면 에러 내용을 확인할 수도 있다.

자세한 내용은 점프 투 파이썬 / 05장 파이썬 날개달기 / 05-4 예외 처리 를 확인하자.

그리고 예외처리는 하지 않지만 무조건 수행해야 할 작업이 있을 경우 그냥 Try-Finally 이 2개로 코드를 짜는 코딩 패턴도 있다.

raise 키워드로 직접 예외 발생시키기

중요한 걸 체크하기 위해 일부러 raise 키워드로 예외를 발생시켜 처리할 수도 있다. 예를 들어 ‘Lee’가 아니면 ValueError를 발생시켜 확인하자! 이런 전략이 있다면 이렇게 써볼 수 있겠다.

name = 'Kim'

try:
    if name == 'Lee':
        print('OK')
    else:
        raise ValueError
except ValueError:
    print('Error!')
except Exception as e:
    print(e)
else:
    print('Completed!')

파이썬 예외처리 끝.

추천 글

댓글 남기기