16. 예외 처리

예외란 말 그대로 프로그램에서 벌어지는 예외적인 상황을 말합니다. 예를 들면 읽을려는 파일이 존재하지 않는 경우, 또는 프로그램이 한참 실행 중에 파일이 갑자기 지워지는 경우입니다. 이러한 상황을 처리하는 것을 예외 처리라고 합니다.

비슷하게 여러분의 프로그램에 존재하지 않는 명령문이 있을 경우 어떻게 될까요? 이 경우 파이썬은 손을 들고(raise) 프로그램에 오류(error) 가 있다고 알려 줍니다.


16.1 오류

간단한 print 함수를 호출하는 상황을 생각해 봅시다. 이때 print를 Print라고 잘못 코딩했을 경우 어떻게 될까요? (대/소문자 구분에 유의하세요.) 이 경우, 파이썬은 구문 오류를 발생시킵니다.

>>> Print "Hello World"
  File "<stdin>", line 1
    Print "Hello World"
                      ^
SyntaxError: invalid syntax
>>> print "Hello World"
Hello World


이와 같이 SyntaxError(구문오류)가 발생되었고 오류가 발생한 위치가 표시됩니다. 이것은 이 오류의 오류 핸들러에 의해 처리된 것입니다.


16.2 예외

이번에는 사용자로부터 뭔가를 입력받는 것을 시도하는(try) 경우를 생각해 봅시다. 이 때 ctrl-d를 누르고 어떻게 되는지 살펴봅시다.

>>> s = raw_input('Enter something --> ')
Enter something --> Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
EOFError s = raw_input('En


그러면 파이썬은 EOFError라고 불리우는 오류를 발생시키는데 이때 EOF란 파일의 끝end of file을 의미하며(파일의 끝은 ctrl-d에 의해 표현됩니다), 갑자기 파일의 끝이 올 것을 예상하지 못했기 때문에 이러한 오류가 발생하는 것입니다.


16.3 예외 처리

예외는 try..except 문을 통해 처리할 수 있습니다. 이것은 try 블록 안에 평소와 같이 명령을 입력하고 예외 상황에 해당하는 오류 핸들러를 except 블록에 입력해 주면 됩니다.


https://github.com/swaroopch/byte-of-python/blob/master/programs/exceptions_handle.py

try:
    text = raw_input('Enter something --> ')
except EOFError:
    print 'Why did you do an EOF on me?'
except KeyboardInterrupt:
    print 'You cancelled the operation.'
else:
    print 'You entered {}'.format(text)
exceptions_handle.py(이 코드를 exceptions_handle.py로 저장하세요)

실행 결과는 다음과 같습니다.

# Press ctrl + d
$ python exceptions_handle.py
Enter something --> Why did you do an EOF on me?

# Press ctrl + c
$ python exceptions_handle.py
Enter something --> ^CYou cancelled the operation.

$ python exceptions_handle.py
Enter something --> No exceptions
You entered No exceptions


이 예제에서는 예외가 발생할 수 있는 모든 명령문을 try 블록에 넣었으며 오류/예외를 적절하게 처리해 줄 핸들러를 except 절/블록에 두었습니다. except 절에서는 지정된 한 개의 오류 혹은 예외를 처리할 수 있고, 괄호로 묶여진 모든 오류/예외 목록을 처리할 수도 있습니다. 만일 오류/예외가 지정되지 않은 경우에는 모든 오류/예외를 처리하게 됩니다.

이때 모든 try 절에는 적어도 한 개의 except 절이 있어야 합니다. 아니면 try 블록을 사용할 아무런 이유가 없겠지요?

만약 어떤 오류나 예외든지 이처럼 처리되지 않는 경우, 기본 파이썬 오류 핸들러가 호출됩니다. 그러면 파이썬 오류  핸들러에 의해 프로그램 수행이 중단되며 해당 오류 메시지가 출력됩니다. 앞서 기본 파이썬 오류 핸들러가 어떻게 동작하는지 보았었죠.

try..except 블록에는 추가로 else 절을 붙일 수 있습니다. else 절은 어떤 예외도 발생하지 않았으면 호출됩니다.

다음으로는 예외 객체를 얻어오는 방법과 이를 통해 추가 정보를 알아내는 방법에 대해 알아봅니다.


16.4 예외 발생시키기

raise 문에 오류/예외의 이름을 넘겨주는 것을 통해 예외를 직접 발생(raise) 시킬 수 있습니다. 그러면 예외 객체가 throw 됩니다.

이때 발생시킬 수 있는 오류나 예외는 반드시 직접적으로든, 간접적으로든 Exception 클래스에서 파생된 클래스여야 합니다.


https://github.com/swaroopch/byte-of-python/blob/master/programs/exceptions_raise.py

class ShortInputException(Exception):
    '''A user-defined exception class.'''
    def __init__(self, length, atleast):
        Exception.__init__(self)
        self.length = length
        self.atleast = atleast

try:
    text = raw_input('Enter something --> ')
    if len(text) < 3:
        raise ShortInputException(len(text), 3)
    # Other work can continue as usual here
except EOFError:
    print 'Why did you do an EOF on me?'
except ShortInputException as ex:
    print ('ShortInputException: The input was ' + \
           '{0} long, expected at least {1}')\
          .format(ex.length, ex.atleast)
else:
    print 'No exception was raised.'
exceptions_raise.py(이 코드를 exceptions_raise.py로 저장하세요)

실행 결과는 다음과 같습니다.

$ python exceptions_raise.py
Enter something --> a
ShortInputException: The input was 1 long, expected at least 3

$ python exceptions_raise.py
Enter something --> abc
No exception was raised.


이 예제에서는 ShortInputException이라는 새로운 예외 형식을 직접 하나 만들었습니다. 여기에는 두 개의 필드가 있습니다. 하나는 length 필드로 주어진 입력의 길이를 의미하며, 다른 하나는 atleast 필드로 프로그램이 요구하는 최소한의 길이를 의미합니다.

이제 except 절에서 as를 이용하여 해당 오류의 클래스를 좀 더 짧은 이름의 변수로 대신하여 사용할 수 있습니다. 여기서 새로 정의한 예외 형식에 정의한 필드와 값의 관계는 마치 함수에서의 매개 변수와 인수의 관계와 비슷합니다. 마지막으로 이 오류를 처리해주는 except 절에서는 해당 예외 객체의 length 와 atleast 필드를 이용하여 사용자에게 적절한 결과를 출력해 줍니다.


16.5 Try …​ Finally 문

프로그램이 파일을 읽고 있는 상황을 가정해 봅시다. 이 때 예외가 발생할 경우, 예외의 발생 여부와 상관없이 파일 객체를 항상 닫아 주도록 할 수는 없을까요? 이를 위해 finally 블록을 사용합니다.


https://github.com/swaroopch/byte-of-python/blob/master/programs/exceptions_finally.py

import sys
import time

f = None
try:
    f = open("poem.txt")
    # Our usual file-reading idiom
    while True:
        line = f.readline()
        if len(line) == 0:
            break
        print line,
        sys.stdout.flush()
        print "Press ctrl+c now"
        # To make sure it runs for a while
        time.sleep(2)
except IOError:
    print "Could not find file poem.txt"
except KeyboardInterrupt:
    print "!! You cancelled the reading from the file."
finally:
    if f:
        f.close()
    print "(Cleaning up: Closed the file)"
exceptions_finally.py(이 코드를 exceptions_finally.py로 저장하세요)

실행 결과는 다음과 같습니다.

$ python exceptions_finally.py
Programming is fun
Press ctrl+c now
^C!! You cancelled the reading from the file.
(Cleaning up: Closed the file)


아주 평범한 파일을 읽는 코드를 작성했지만, 파일에서 한 줄을 읽어올 때마다 time.sleep 함수를 호출하여 2초씩 멈추게 하는 인위적인 코드를 집어넣어 프로그램이 천천히 실행되도록 했습니다 (파이썬은 원래 굉장히 빠릅니다). 프로그램이 실행될 때, ctrl + c를 눌러 프로그램을 강제로 중단시켜 봅시다.

그러면 KeyboardInterrupt 예외가 발생되며 프로그램이 종료됩니다. 그러나 프로그램이 종료되기 전에 finally 절이 실행되므로 파일 객체가 항상 닫히게 됩니다.

여기서 print 문 뒤에 sys.stdout.flush()를 사용하여 화면에 결과를 바로바로 출력하도록 해 주었습니다.


16.6 with 문

try 블록에서 시스템 자원을 가져오고 finally 문에서 이를 해제하여 주는 것은 공통된 패턴입니다. 그렇지만, with 문을 이용하면 이러한 패턴을 좀 더 깔끔하게 작성할 수 있습니다.


https://github.com/swaroopch/byte-of-python/blob/master/programs/exceptions_using_with.py

with open("poem.txt") as f:
    for line in f:
        print line,
exceptions_using_with.py(이 코드를 exceptions_using_with.py로 저장하세요)

이 예제는 이전의 예제와 동일한 결과를 출력합니다. 차이점은 open 함수를 사용할 때 with 문을 사용하였다는 것입니다. 그러면 파일을 직접 닫지 않아도 with open이 자동으로 파일을 닫습니다.

그러면 with 문은 어떻게 자동으로 이러한 것을 처리하는 것일까요? 우선 with 문은 open 문이 반환해 주는 객체를 받아 오는데, 여기서는 이것을 'thefile'이라고 하겠습니다.

with 문은 항상 thefile.enter 함수를 호출한 뒤 해당 블록의 코드를 실행하며, 실행이 끝난 후에는 항상 thefile.exit가 호출됩니다.

따라서 finally 블록에 작성한 코드가 exit 메소드에 의해 자동적으로 다루어져야 할 경우에만 이를 사용할 수 있습니다. 이 경우 앞서 설명한 방법대로 하면 매번 try..finally 문을 명시적으로 쓰지 않고도 같은 일을 할 수 있습니다.

이와 관련하여 좀 더 자세한 설명은 이 책이 다루는 범위를 벗어납니다. 그래도 좀 더 알고 싶다면 PEP 343 을 읽어 보시기 바랍니다.


16.7 요약

지금까지 try..except 문과 try..finally 문의 사용법을 살펴봤습니다. 또 사용자 정의 예외 형식을 만드는 법과 예외를 일으키는 법에 대해서도 알아보았습니다.

다음으로, 파이썬 표준 라이브러리를 살펴봅니다.