ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 14. 파이썬 에러와 예외처리 (1)
    파이썬(Python) 강의 2019. 3. 16. 13:34

    이번 포스트에서는 파이썬에러의 종류와 에러 메시지를 읽는 법에 대해서 알아보도록 한다.

    들어가기전에

     이 포스트는 여러분이 파이썬을 설치했고 각자 원하는 IDE를 이용해 파이썬을 개발한다고 가정한다. 혹시 파이썬을 설치하지 않았다면  1.파이썬 설치 및 개발환경 설정 이 포스트에서 파이썬과 파이썬 개발환경을 먼저 셋업하길 바란다. 앞서 말했듯이 반드시 실습 해 보길 바란다. 이 포스트는 여러분이 파이썬의 주요 자료형과 함수를 알고있고 사용 할 수 있다고 가정한다.

    목표

    • 파이썬 에러
    • 구문 에러 (Syntax Error)
    • 런타임 에러 (Runtime Error)
    • 구문 에러 vs 런타임 에러

    파이썬 에러

     보통 프로그래밍언어/프로그램에서 발생하는 에러는 두가지가 존재한다. 첫번재는 구문에러(Syntax Error)이고, 두번째는 실행에러(Runtime Error)이다. 구문에러는 컴파일 시 나는 문법에서 나는 에러이고, 실행 에러는 프로그램 실행 도중에 나는 에러이다. 따라서 컴파일 해야 하는 언어들은 컴파일 당시에 구문에러가 나고, 구문에러를 고친 후 컴파일이 완료되면 실행을 시킬 수 있다. 그러다 실행 중에 에러가 나면 그것은 실행 에러이다. 따라서 컴파일 해야 하는 언어들의 경우 구문에러와 실행에러의 구분이 거의 명확하다. 인터프리터에서는 어떨까? 

     파이썬 인터프리터와 같은 인터프리터의 경우에는 1) 한 줄을 해석해 기계어(또는 바이트코드 또는 다른 중간언어)로 통역하고 2) 해석된 기계어(또는 바이트코드 또는 다른 중간언어)를 실행시킨다고 했다. 이 때, 1)에서 발생하는 에러가 구문에서(Syntax Error)이고, 2)에서 발생하는 에러를 예외(Exception)이라고 부른다. 

     파이썬은 인터프리터가 어떻게 구현됐느냐에 따라 다양한 다른 중간언어로 번역 될 수 있다. 예를들어서 CPython인터프리터는 c언어로 구현된 파이썬 인터프리터로 py파일을 바이트코드(bytecode)로 한번 컴파일 한 후 이를 실행한다. Jython은 자바로 구현된 파이썬 인터프리터로 파이썬을 바이트코드로 컴파일하고 이 바이트코드를 JVM에서 실행 시킬 수 있다. (위처럼 한 줄을 바로 기계어로 번역하는 경우는 요즘엔 거의 없다고 한다..) 따라서 정확히 어느 시점에서 구문 에러가 나느냐는 어떤 인터프리터를 사용하느냐에 따라, 또 중간언어로 컴파일 되느냐의 여부에 따라 다를 것이다. 하지만 개념적으로 봤을 때, 어떤 구문을 해석하는 도중 발생하면 구문 에러, 명령어를 실행하는 도중에 나면 실행 에러라는 점에는 변함이 없다. 

    구문 에러 (SyntaxError)

     구문 에러의 경우는 대부분 오타나 들여쓰기(인덴션) 문제이다. 괄호를 빼먹었거나, 들여쓰기를 잘못하는 것 처럼 '문법'에 맞지 않게 문장을 작성 한 경우 난다.

    print"Hello World!") 실행 결과: File "/Users/fsoftwareengineer/PycharmProjects/MyFirstProject/helloworld.py", line 1 print"Hello World!") ^ SyntaxError: invalid syntax

     위의 경우 '(' 괄호를 적어주지 않아 문법에 맞지 않으므로 SyntaxError가 난다. 

    런타임 에러 (Runtime Error)

     런타임 에러란 파이썬 인터프리터가 번역한 명령어가 실행 도중에 에러가 나는 경우이다. 대표적으로 다음과 같은 경우들이 있다.

    ZeroDivisionError: division by zero

    val = 3/0
    print(val) 실행 결과: Traceback (most recent call last): File "/Users/fsoftwareengineer/PycharmProjects/MyFirstProject/helloworld.py", line 1 val = 3/0 ZeroDivisionError: division by zero

     수학에서는 어떤 숫자를 0으로 나눌 수 없다. 마찬가지로 0으로 어떤 수를 나누려고 한다면 ZeroDivisionError가 난다. 

    IndexError: list index out of range

    list = []
    print(list[0]) 실행 결과: Traceback (most recent call last): File "/Users/fsoftwareengineer/PycharmProjects/MyFirstProject/helloworld.py", line 2 print(list[0]) IndexError: list index out of range

     위의 에러는 list를 선언했지만 내부에 아무 값도 없는경우 난다. 

    NameError: name '  ' is not defined

    print(val) 실행 결과: Traceback (most recent call last): File "/Users/fsoftwareengineer/PycharmProjects/MyFirstProject/helloworld.py", line 1 print(val) NameError: name 'val' is not defined

     이 값은 선언되지 않은 변수를 참조 할 때 나는 에러이다.

     이 밖에도 여러가지 에러가 있지만 이 포스트에서 생략하겠다. 프로그래밍을 하다보면 여러가지 에러를 만나게 될 것이고 대부분의 경우는 위와같은 런타임 에러(Runtime Error)일 것이다. 이런 런타임 에러를 보통 예외(Exception)이라고 부른다. 

    Traceback

     예외는 실행 도중 나기 때문에 보통 'Traceback' 정보를 제공한다. Traceback는 예를들어서 예외가 난 부분이 내 함수가 아니라, 내 함수 안의 다른 함수 내부에서 난 경우 정확히 어느 함수가 뭘 실행하다가 이 예외가 났는지 알려주는 함수의 콜 체인(call chain)이다.

    def divide(x, y):
    return x/y

    def func(x, y):
    return divide(x, y)


    func(1, 2)
    func(1, 0) 실행 결과: Traceback (most recent call last): File "/Users/-/PycharmProjects/MyFirstProject/helloworld.py", line 9, in <module> func(1, 0) File "/Users/-/PycharmProjects/MyFirstProject/helloworld.py", line 5, in func return divide(x, y) File "/Users/-/PycharmProjects/MyFirstProject/helloworld.py", line 2, in divide return x/y ZeroDivisionError: division by zero

     func을 실행하면 func이 divide라는 함수를 실행한다. Traceback의 첫번째 줄을 보면 9번째 라인에서 y에 0를 넣어주는 것을 확인 할 수 있다. 그리고 이 y가 5번 라인에서 divide로 넘어가고, 2번라인에서 연산을 실행하다가 에러가 나는것을 확인 할 수 있다. 만약 파이썬 인터프리터가 Traceback을 제공하지 않고 아래와 같은 메시지만 보여준다고 하자. 

    File "/Users/-/PycharmProjects/MyFirstProject/helloworld.py", line 2, in divide return x/y ZeroDivisionError: division by zero

     이렇게 되면 func(1, 2) 때문에 에러가 났는지, func(1, 0)때문에 에러가 났는지 알 수 없다. 지금은 예제라서 1, 2 또는 1, 0 이렇게 숫자를 넣어줬지만 실제로 프로그래밍을 할 때는 변수를 넣어 줄 확률이 높다. 그러면 위의 예외 메시지만 가지고 어디에서 문제가 발생했는지 찾기 매우 어려울 것이다. 이것이 바로 파이썬 또는 대부분의 언어들이 Traceback(함수의 콜 체인)을 제공하는 이유이다.

    구문 에러 vs 런타임 에러

     아래와 같은 코드를 실행 해 보자. value라는 값이 존재하지 않으므로 NameError라는 런타임 에러가 난다.

    val = 5
    if val > 3 :
    print(value + 1)
    else :
    print(val - 1) 실행 결과: Traceback (most recent call last): File "/Users/-/PycharmProjects/MyFirstProject/helloworld.py", line 3, in <module> print(value + 1) NameError: name 'value' is not defined

     이번에는 아래를 실행 해 보자.

    val = 2
    if val > 3 :
    print(value + 1)
    else :
    print(val - 1) 실행 결과: 1

     에러가 나지 않고 실행 되는 것을 확인 할 수 있다. 왜인가? val이 3보다 크지 않았기 때문에 print(value + 1)부분을 실행하지 않았기 때문이다! 실행(런타임)하지 않았으므로 에러가 날 일도 없다. 위에서는 val = 5이기 째문에 첫번째 if 블록으로 들어가 에러가 나는 부분이 실행 돼 실행(런타임) 에러가 난 것이다.

    이번엔 아래를 실행 해 보자.

    val = 5
    if val > 3 :
    print(val + 1)
    else :
    printval - 1) 실행 결과: File "/Users/-/PycharmProjects/MyFirstProject/helloworld.py", line 5 printval - 1) ^ SyntaxError: invalid syntax

     얘는 val이 3보다 커서 else가 실행 될 일이 없음에도 불구하고 에러가 난다. 또 다른 경우도 해 보자.

    val = 2
    if val > 3 :
    printval + 1)
    else :
    print(val - 1) 실행 결과: File "/Users/-/PycharmProjects/MyFirstProject/helloworld.py", line 3 printval + 1) ^ SyntaxError: invalid syntax

     마찬가지로 해당 if블록이 실행되지 않아도 구문 에러가 나는 것을 확인 할 수 있다. 

    왜 구문에러는 실행에러처럼 실행이 안되는 경우 무시되지 않는걸까? 파이썬이 처음 인터프리터를 실행하면 파싱을 위해 이 파일을 분석한다. 분석 과정에서 파이썬 인터프리터가 이해하지 못하는 문법이 나오면 실행을 중지하고 SyntaxError를 낸다. 더 자세한 사항은 렉서(Lexer), 파서(Parser), 추상구문트리(Abstract Syntax Tree)등의 컴파일러 이론을 알아야 하므로 깊게 다루진 않겠다. 만약 여러분이 컴퓨터 공학도이고 컴파일러와 프로그래밍 언어를 배우는 중이라면 스스로 더 찾아보기를 권한다.

     이번 포스트를 통해서 에러의 종류를 알아보고, 파이썬에서 나는 에러의 종류에 대해 알아보았다. 구문에러/런타임에러는 꼭 파이썬에서만 나는 에러가 아니고 대부분의 프로그래밍 언어에서 나는 에러이다. 또 우리가 이 포스트에서 다루지 않은 세번째 에러가 있다. 바로 로직 에러(Logic Error)이다. 우리가 보통 버그라고 부르는 대부분의 에러들이 로직 에러이다. 로직 에러의 원인은 우리이다. 우리가 잘못된 로직으로 프로그래밍해 프로그램이 요구사항대로 돌아가지 않는 것을 버그라고 부른다. 그리고 그런 로직 에러를 찾는 것을 디버깅(Debugging)이라고 부른다.

    다음 포스트에서는 위의 에러를 처리하는 법에 대해서 알아보도록 하겠다.



    댓글

f.software engineer @ All Right Reserved