부제목


8. 에러와 예외

지금까지 에러 메시지는 한 번도 언급하지 않았습니다. 그러나 예제들을 시험해 보았다면 아마도 몇 번은 에러 메시지를 보았을 것입니다. (적어도) 에러 메시지는 크게 두 가지 종류로 나뉩니다: 구문 에러예외로 나뉩니다.


8.1 구문 에러

이른바 해석 에러라고도 알려져 있는 구문 에러는 아마도 지금 파이썬을 배우고 있는 동안 가장 흔이 맞이하는 종류의 불평일 것입니다:

>>> while True print 'Hello world'
  File "<stdin>", line 1, in ?
    while True print 'Hello world'
                   ^
SyntaxError: invalid syntax

파이썬은 에러가 일어난 줄을 다시 보여주고 작은 `윗꺽쇠'를 화면에 표시해서 에러가 탐지된 줄에서 가장 앞 지점을 가리킵니다. 에러가 야기된 곳은 (또는 적어도 탐지된 곳은) 윗꺽쇠 앞의 토큰입니다: 예제에서, 에러는 print 키워드에서 탐지되었는데, 쌍점(":")이 그 앞에 빠져 있기 때문입니다. 파일 이름과 줄 번호가 인쇄되므로 입력이 스크립트로부터 온 것이라면 에러를 어디에서 찾아야 할지 알 수 있습니다.


8.2 예외

서술문이나 표현식이 구문적으로 올바르더라도, 실행을 해 보려고 하면 에러를 야기할 수 있습니다. 실행시간에 탐지되는 에러를 예외(exceptions)라고 부르며 통제하지 못할 정도로 심각합니다: 앞으로 파이썬에서 예외를 처리하는 법을 배울 것입니다. 대부분의 예외는 프로그램에서 처리하지 않습니다. 에러 메시지의 결과는 아래에 보이는 바와 같습니다:

>>> 10 * (1/0)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
ZeroDivisionError: integer division or modulo by zero
>>> 4 + spam*3
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
NameError: name 'spam' is not defined
>>> '2' + 2
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: cannot concatenate 'str' and 'int' objects

에러 메시지의 마지막 줄에서 무슨 일이 일어났는지 보여줍니다. 예외는 다양한 형태로 일어납니다. 그 유형이 메시지의 일부로 인쇄됩니다: 예제에서의 유형은 ZeroDivisionErrorNameError 그리고 TypeError입니다. 예외 유형으로 인쇄된 문자열은 일어난 내장 예외의 이름입니다. 이는 모든 내장 예외에 적용됩니다. 그러나 사용자-정의 예외에 적용할 필요는 없습니다 (물론 유용한 관례이긴 합니다). 표준 예외 이름들은 (예약된 키워드가 아니라) 내장된 식별자입니다.

나머지 줄은 예외의 유형과 야기한 원인에 근거하여 자세하게 설명합니다.

에러 메시지의 앞 부분은 스택 역추적의 형태로 예외가 일어난 상황을 보여줍니다. 에러 메시지에는 일반적으로 소스 줄을 나열하는 스택 역추적이 포함되어 있습니다; 그렇지만, 표준 입력으로부터 읽은 줄들은 화면에 표시하지 않습니다.

파이썬 라이브러리 참조서에 내장 예외와 그 의미가 나열되어 있습니다.


8.3 예외 처리

선택된 예외를 처리하는 프로그램을 작성할 수 있습니다. 다음 예제를 보시면, 올바른 정수 값이 입력될 때까지 사용자에게 입력을 요구하고 있습니다. 그러나 사용자는 (Control-C를 사용하거나 운영 체제에서 지원하는 무엇이든 이용하여) 프로그램을 중지시킬 수 있습니다; 사용자가-발생시킨 인터럽트는 KeyboardInterrupt 예외로 전송됨에 주의하세요.

>>> while True:
...     try:
...         x = int(raw_input("Please enter a number: "))
...         break
...     except ValueError:
...         print "Oops!  That was no valid number.  Try again..."
...

try 서술문은 다음과 같이 작동합니다.

try 서술문에는 하나 이상의 except 절이 있을 수 있습니다. 다양한 예외에 대하여 처리자를 지정하기 위해서 말입니다. 적어도 처리자 하나는 실행됩니다. 처리자는 그에 상응하는 try절에서 일어난 예외만 처리합니다. 같은 try 서술문의 다른 처리자에서 일어난 예외는 처리하지 않습니다. except 절에는 반괄호로 둘러 싸인 터플로 여러 예외에 이름을 줄 수 있습니다. 예를 들어:

... except (RuntimeError, TypeError, NameError):
...     pass

마지막 except 절은 예외 이름(들)을 생략해도 됩니다. 만능문자로 기여하기 위해서 말입니다. 이는 아주 조심해서 사용하셔야 합니다. 왜냐하면 이런 식으로 하면 진짜 프로그래밍 에러를 가리기 쉽기 때문입니다! 또한 에러 메시지를 인쇄한 다음 그 예외를 재발생시키는데 사용할 수도 있습니다 (그 덕분에 호출자도 역시 그 예외를 처리할 수 있습니다):

import sys

try:
    f = open('myfile.txt')
    s = f.readline()
    i = int(s.strip())
except IOError, (errno, strerror):
    print "I/O error(%s): %s" % (errno, strerror)
except ValueError:
    print "Could not convert data to an integer."
except:
    print "Unexpected error:", sys.exc_info()[0]
    raise

try ... except 서술문은 선택적으로 else 절을 가질 수 있는데, 이 절은 존재한다면 모든 except 절 다음에 나와야 합니다. try 절이 예외를 일으키지 않을 경우에도 반드시 실행되어야 하는 코드에 유용합니다. 예를 들어:

for arg in sys.argv[1:]:
    try:
        f = open(arg, 'r')
    except IOError:
        print 'cannot open', arg
    else:
        print arg, 'has', len(f.readlines()), 'lines'
        f.close()

else 절을 사용하는 편이 try 절에 except: 코드를 추가하는 것보다 더 좋습니다. 왜냐하면 try ... except 서술문으로 보호되고 있는 코드가 일으키지 않은 예외를 우연하게 잡지 않도록 방지해 주기 때문입니다.

예외가 일어나면, 연관 값을 가질 수 있는데, 이를 예외의 인자라고도 부릅니다. 인자의 존재와 유형은 그 예외의 유형에 달려 있습니다.

except 절은 예외 이름 (또는 터플) 다음에 변수를 지정할 수 있습니다. 이 변수는 인자를 instance.args에 저장하고 있는 예외 실체에 묶입니다. 편의를 위해, 예외 실체에는 __getitem____str__이 정의되어 있어서 .args로 참조하지 않아도 인자들에 직접 접근하거나 인쇄할 수 있습니다.

그러나 .args를 사용하는 것은 별로 좋은 방법이 아닙니다. 대신에, 인자 하나를 예외에 건네고 (여러 인자가 필요하면 터플로 건네고) 그리고 그것을 message 속성에 묶는 방법이 더 좋습니다. 또 예외를 먼저 실체화한 다음에 그 예외를 일으키고 원하는대로 속성을 거기에 추가할 수도 있습니다.

>>> try:
...    raise Exception('spam', 'eggs')
... except Exception, inst:
...    print type(inst)     # 예외 실체
...    print inst.args      # .args에 저장된 인자
...    print inst           # __str__ 덕분에 args를 직접 인쇄할 수 있다
...    x, y = inst          # __getitem__ 덕분에 args를 직접 풀 수 있다
...    print 'x =', x
...    print 'y =', y
...
<type 'exceptions.Exception'>
('spam', 'eggs')
('spam', 'eggs')
x = spam
y = eggs

예외에 인자가 있다면, 예외가 처리되지 못한 경우 메시지의 마지막 부분(`detail')에 인쇄됩니다.

예외 처리자는 예외가 try 절에서 일어나면 그 즉시 처리할 뿐만 아니라, try 절에서 (간접적으로라도) 호출된 함수 안에서 일어나더라도 처리합니다. 예를 들어:

>>> def this_fails():
...     x = 1/0
... 
>>> try:
...     this_fails()
... except ZeroDivisionError, detail:
...     print 'Handling run-time error:', detail
... 
Handling run-time error: integer division or modulo by zero


8.4 예외 일으키기

raise 서술문으로 프로그래머는 지정된 예외를 강제로 일으킬 수 있습니다. 예를 들어:

>>> raise NameError, 'HiThere'
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
NameError: HiThere

raise에 건넨 첫 인자는 일으킬 예외의 이름을 지정합니다. 선택적인 두 번재 인자는 예외의 인자를 지정합니다. 다른 방식으로 위의 예제는 raise NameError('HiThere')와 같이 쓸 수 있습니다. 어느 형태이든 잘 작동하지만, 후자가 아무래도 많이 사용하는 스타일인 듯 보입니다.

예외가 일어났는지 결정은 해야 하나 그것을 처리하고 싶지는 않다면, 더 단순한 형태의 raise 서술문으로 그 예외를 다시 일으킬 수 있습니다:

>>> try:
...     raise NameError, 'HiThere'
... except NameError:
...     print 'An exception flew by!'
...     raise
...
An exception flew by!
Traceback (most recent call last):
  File "<stdin>", line 2, in ?
NameError: HiThere


8.5 사용자-정의 예외

따로 새로운 예외 클래스를 만들어서 예외에 이름을 줄 수 있습니다. 예외는 전형적으로 직접적이든 간접적이든 Exception 클래스로부터 파생되어야 합니다. 예를 들어:

>>> class MyError(Exception):
...     def __init__(self, value):
...         self.value = value
...     def __str__(self):
...         return repr(self.value)
... 
>>> try:
...     raise MyError(2*2)
... except MyError, e:
...     print 'My exception occurred, value:', e.value
... 
My exception occurred, value: 4
>>> raise MyError, 'oops!'
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
__main__.MyError: 'oops!'

이 예제에서는 Exception의 기본 __init__이 오버라이드 되었습니다. 새로운 행위는 그저 value 속성을 만드는 것입니다. 이렇게 해서 args 속성을 만드는 기본 행위를 교체합니다.

예외 클래스는 다른 클래스가 할 수 있는 일이면 무엇이든 정의될 수 있습니다. 그러나 보통 단순하게 유지되고, 종종 수 많은 속성을 제공하여서 예외 처리자가 에러에 관한 정보를 추출할 수 있도록 해 줍니다. 여러개의 다른 에러들을 일으킬 수 있는 모듈을 만들 때, 가장 흔한 관례는 그 모듈이 정의한 예외에 대하여 바탕 클래스를 만들고 그것을 하부클래스화하여 다양한 에러 조건에 대하여 특정한 예외 클래스를 만들어 내는 것입니다:

class Error(Exception):
    """이 모듈의 예외들을 위한 바탕 클래스."""
    pass

class InputError(Error):
    """입력 에러에 대하여 발생하는 예외.

    Attributes:
        expression -- 에러가 일어난 입력 표현식
        message -- 에러에 대한 설명
    """

    def __init__(self, expression, message):
        self.expression = expression
        self.message = message

class TransitionError(Error):
    """허용되지 않은 상태로 전이를 시도할 때
    일어남.

    속성:
        previous -- 전이의 시작 상태
        next -- 새로운 상태 시도
        message -- 왜 특정한 전이가 허용되지 않는지 설명
    """

    def __init__(self, previous, next, message):
        self.previous = previous
        self.next = next
        self.message = message

대부분의 예외는 이름의 끝이 ``Error''로 정의되는데, 표준 예외의 이름과 비슷합니다.

많은 표준 모듈에는 독자적으로 예외를 정의하고 있어서 자신이 정의한 함수에 일어난 에러를 보고합니다. 클래스에 관한 더 자세한 정보는 9, ``클래스''에서 배워 보겠습니다.


8.6 청소 조치 정의하기

try 서술문은 모든 상황 아래에서 반드시 실행되어야 할 청소 조치를 정의하고자 선택적으로 또다른 절이 있습니다. 예를 들어:

>>> try:
...     raise KeyboardInterrupt
... finally:
...     print 'Goodbye, world!'
... 
Goodbye, world!
Traceback (most recent call last):
  File "<stdin>", line 2, in ?
KeyboardInterrupt

finally 절은 예외가 일어나든 안 일어나든 try 서술문을 떠나기 전에 항상 실행됩니다. 예외가 try 절에서 일어났고 except 절에서 처리하지 못했으면 (또는 except 절 또는 else 절에서 일어났으면), finally 절이 실행된 후에 다시 그 예외가 일어납니다. finally 절은 "나가면서" 실행되기도 합니다. breakcontinue 또는 return 서술문을 통하여 try 서술문에서 다른 절들이 남아 있을 경우에도 말입니다. 다음은 좀 더 복잡한 예입니다 (파이썬 2.5에서부터 같은 try 서술문 안에 except절과 finally 절이 있어도 됩니다):

>>> def divide(x, y):
...     try:
...         result = x / y
...     except ZeroDivisionError:
...         print "division by zero!"
...     else:
...         print "result is", result
...     finally:
...         print "executing finally clause"
...
>>> divide(2, 1)
result is 2
executing finally clause
>>> divide(2, 0)
division by zero!
executing finally clause
>>> divide("2", "1")
executing finally clause
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "<stdin>", line 3, in divide
TypeError: unsupported operand type(s) for /: 'str' and 'str'

보시다시피, finally 절은 어떤 사건에서도 실행됩니다. 두 문자열을 나누면서 일어난 TypeError 예외를 except 절이 처리하지 못했고 그러므로 finally 절이 실행을 마친 후에 다시 발생 시킵니다.

실세계의 어플리케이션이라면, finally 절이 (파일이나 네트워크 접속 같은) 외부 자원을 놓아주는데 쓸모가 있습니다. 그 자원을 사용하든 못하든 상관 없이 말입니다.


8.7 미리 정의된 청소 조치

어떤 객체에는 연산이 성공하든 실패하든 상관없이 자신이 더 이상 필요하지 않을 때 표준 청소 조치가 취해지되도록 정의되어 있습니다. 다음 예제를 보시면, 파일을 열어서 그 내용을 화면에 인쇄하려고 합니다.

for line in open("myfile.txt"):
    print line

이 코드의 문제점은 코드가 실행을 마친 후에도 얼마간 파일을 열어 둔다는 것입니다. 간단한 스크립트라면 문제가 없지만, 큰 어플리케이션이라면 문제가 될 수 있습니다. with 서술문과 함께 파일 같은 객체를 사용하면 언제나 즉시 그리고 올바르게 청소된다고 확인할 수 있습니다.

with open("myfile.txt") as f:
    for line in f:
        print line

서술문이 실행을 미치고 나면, 파일 f는 언제나, 줄을 처리하다가 문제에 봉착하더라도 닫힙니다. 미리 정의된 청소 조치를 제공하는 다른 객체들은 자신의 문서에 이 특징을 기술할 것입니다.

주의: with는 새로 도입된 키워드이므로, 파이썬 2.5라면 from __future__ import with_statement를 실행하여 활성화 시켜야 합니다. 2.6부터는 언제나 활성상태입니다.

변경 제안에 관한 정보는 이 문서에 관하여를 참조.