8. 에러와 예외
**************

지금까지 에러 메시지가 언급되지는 않았지만, 예제들을 직접 해보았다면
아마도 몇몇 개를 보았을 것입니다. (적어도) 두 가지 구별되는 에러들이
있습니다; *문법 에러* 와 *예외*.


8.1. 문법 에러
==============

문법 에러는, 파싱 에러라고도 알려져 있습니다, 아마도 여러분이 파이썬을
배우고 있는 동안에는 가장 자주 만나는 종류의 불평일 것입니다:

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

파서는 문제가 되는 줄을 다시 보여주고 줄에서 에러가 감지된 가장 앞의
위치를 가리키는 작은 '화살표'를 표시합니다. 에러는 화살표 *앞에 오는*
토큰이 원인입니다 (또는 적어도 그곳에서 감지되었습니다): 이 예에서, 에
러는 함수 "print()" 에서 감지되었는데, 그 앞에 콜론 ("':'") 이 빠져있
기 때문입니다. 파일 이름과 줄 번호가 인쇄되어서, 입력이 스크립트로부터
올 때 찾을 수 있도록 합니다.


8.2. 예외
=========

문장이나 표현식이 문법적으로 올바르다 할지라도, 실행하려고 하면 에러를
일으킬 수 있습니다. 실행 중에 감지되는 에러들을 *예외* 라고 부르고 무
조건 치명적이지는 않습니다: 파이썬 프로그램에서 이것들을 어떻게 다루는
지 곧 배우게 됩니다. 하지만 대부분의 예외는 프로그램이 처리하지 않아서
, 여기에서 볼 수 있듯이 에러 메시지를 만듭니다:

   >>> 10 * (1/0)
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
   ZeroDivisionError: division by zero
   >>> 4 + spam*3
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
   NameError: name 'spam' is not defined
   >>> '2' + 2
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
   TypeError: can only concatenate str (not "int") to str

에러 메시지의 마지막 줄은 어떤 일이 일어났는지 알려줍니다. 예외는 여러
형으로 나타나고, 형이 메시지 일부로 인쇄됩니다: 이 예에서의 형은
"ZeroDivisionError", "NameError", "TypeError" 입니다. 예외 형으로 인쇄
된 문자열은 발생한 내장 예외의 이름입니다. 이것은 모든 내장 예외들의
경우는 항상 참이지만, 사용자 정의 예외의 경우는 (편리한 관례임에도 불
구하고) 꼭 그럴 필요는 없습니다. 표준 예외 이름은 내장 식별자입니다 (
예약 키워드가 아닙니다).

줄의 나머지 부분은 예외의 형과 원인에 기반을 둔 상세 명세를 제공합니다
.

에러 메시지의 앞부분은 스택 트레이스의 형태로 예외가 일어난 위치의 문
맥을 보여줍니다. 일반적으로 소스의 줄들을 나열하는 스택 트레이스를 포
함하고 있습니다; 하지만, 표준 입력에서 읽어 들인 줄들은 표시하지 않습
니다.

내장 예외 는 내장 예외들과 그 들의 의미를 나열하고 있습니다.


8.3. 예외 처리하기
==================

선택한 예외를 처리하는 프로그램을 만드는 것이 가능합니다. 다음 예를 보
면, 올바를 정수가 입력될 때까지 사용자에게 입력을 요청하지만, 사용자가
프로그램을 인터럽트 하는 것을 허용합니다 ("Control-C" 나 그 외에 운영
체제가 지원하는 것을 사용해서); 사용자가 만든 인터럽트는
"KeyboardInterrupt" 예외를 일으키는 형태로 나타남에 유의하세요.

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

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

* 먼저, *try 절* ("try" 와 "except" 사이의 문장들) 이 실행됩니다.

* 예외가 발생하지 않으면, *except 절* 을 건너뛰고 "try" 문의 실행은 종
  료됩니다.

* If an exception occurs during execution of the "try" clause, the
  rest of the clause is skipped.  Then, if its type matches the
  exception named after the "except" keyword, the *except clause* is
  executed, and then execution continues after the try/except block.

* If an exception occurs which does not match the exception named in
  the *except clause*, it is passed on to outer "try" statements; if
  no handler is found, it is an *unhandled exception* and execution
  stops with a message as shown above.

A "try" statement may have more than one *except clause*, to specify
handlers for different exceptions.  At most one handler will be
executed. Handlers only handle exceptions that occur in the
corresponding *try clause*, not in other handlers of the same "try"
statement.  An *except clause* may name multiple exceptions as a
parenthesized tuple, for example:

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

A class in an "except" clause is compatible with an exception if it is
the same class or a base class thereof (but not the other way around
--- an *except clause* listing a derived class is not compatible with
a base class). For example, the following code will print B, C, D in
that order:

   class B(Exception):
       pass

   class C(B):
       pass

   class D(C):
       pass

   for cls in [B, C, D]:
       try:
           raise cls()
       except D:
           print("D")
       except C:
           print("C")
       except B:
           print("B")

Note that if the *except clauses* were reversed (with "except B"
first), it would have printed B, B, B --- the first matching *except
clause* is triggered.

All exceptions inherit from "BaseException", and so it can be used to
serve as a wildcard. Use this with extreme caution, since it is easy
to mask a real programming error in this way!  It can also be used to
print an error message and then re-raise the exception (allowing a
caller to handle the exception as well):

   import sys

   try:
       f = open('myfile.txt')
       s = f.readline()
       i = int(s.strip())
   except OSError as err:
       print("OS error: {0}".format(err))
   except ValueError:
       print("Could not convert data to an integer.")
   except BaseException as err:
       print(f"Unexpected {err=}, {type(err)=}")
       raise

Alternatively the last except clause may omit the exception name(s),
however the exception value must then be retrieved from
"sys.exc_info()[1]".

The "try" ... "except" statement has an optional *else clause*, which,
when present, must follow all *except clauses*.  It is useful for code
that must be executed if the *try clause* does not raise an exception.
For example:

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

"else" 절의 사용이 "try" 절에 코드를 추가하는 것보다 좋은데, "try" ...
"except" 문에 의해 보호되고 있는 코드가 일으키지 않은 예외를 우연히 잡
게 되는 것을 방지하기 때문입니다.

예외가 발생할 때, 연관된 값을 가질 수 있는데, 예외의 *인자* 라고도 알
려져 있습니다. 인자의 존재와 형은 예외 형에 의존적입니다.

The *except clause* may specify a variable after the exception name.
The variable is bound to an exception instance with the arguments
stored in "instance.args".  For convenience, the exception instance
defines "__str__()" so the arguments can be printed directly without
having to reference ".args".  One may also instantiate an exception
first before raising it and add any attributes to it as desired.

   >>> try:
   ...     raise Exception('spam', 'eggs')
   ... except Exception as inst:
   ...     print(type(inst))    # the exception instance
   ...     print(inst.args)     # arguments stored in .args
   ...     print(inst)          # __str__ allows args to be printed directly,
   ...                          # but may be overridden in exception subclasses
   ...     x, y = inst.args     # unpack args
   ...     print('x =', x)
   ...     print('y =', y)
   ...
   <class 'Exception'>
   ('spam', 'eggs')
   ('spam', 'eggs')
   x = spam
   y = eggs

예외가 인자를 가지면, 처리되지 않은 예외 메시지의 마지막 부분('상세 명
세')에 인쇄됩니다.

Exception handlers don't just handle exceptions if they occur
immediately in the *try clause*, but also if they occur inside
functions that are called (even indirectly) in the *try clause*. For
example:

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


8.4. 예외 일으키기
==================

"raise" 문은 프로그래머가 지정한 예외가 발생하도록 강제할 수 있게 합니
다. 예를 들어:

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

"raise" 에 제공하는 단일 인자는 발생시킬 예외를 가리킵니다. 예외 인스
턴스이거나 예외 클래스 ("Exception" 를 계승하는 클래스) 이어야 합니다.
예외 클래스가 전달되면, 묵시적으로 인자 없이 생성자를 호출해서 인스턴
스를 만듭니다:

   raise ValueError  # shorthand for 'raise ValueError()'

만약 예외가 발생했는지는 알아야 하지만 처리하고 싶지는 않다면, 더 간단
한 형태의 "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 <module>
   NameError: HiThere


8.5. 예외 연쇄
==============

If an unhandled exception occurs inside an "except" section, it will
have the exception being handled attached to it and included in the
error message:

   >>> try:
   ...     open("database.sqlite")
   ... except OSError:
   ...     raise RuntimeError("unable to handle error")
   ...
   Traceback (most recent call last):
     File "<stdin>", line 2, in <module>
   FileNotFoundError: [Errno 2] No such file or directory: 'database.sqlite'

   During handling of the above exception, another exception occurred:

   Traceback (most recent call last):
     File "<stdin>", line 4, in <module>
   RuntimeError: unable to handle error

To indicate that an exception is a direct consequence of another, the
"raise" statement allows an optional "from" clause:

   # exc must be exception instance or None.
   raise RuntimeError from exc

이것은 예외를 변환할 때 유용할 수 있습니다. 예를 들면:

   >>> def func():
   ...     raise ConnectionError
   ...
   >>> try:
   ...     func()
   ... except ConnectionError as exc:
   ...     raise RuntimeError('Failed to open database') from exc
   ...
   Traceback (most recent call last):
     File "<stdin>", line 2, in <module>
     File "<stdin>", line 2, in func
   ConnectionError

   The above exception was the direct cause of the following exception:

   Traceback (most recent call last):
     File "<stdin>", line 4, in <module>
   RuntimeError: Failed to open database

It also allows disabling automatic exception chaining using the "from
None" idiom:

   >>> try:
   ...     open('database.sqlite')
   ... except OSError:
   ...     raise RuntimeError from None
   ...
   Traceback (most recent call last):
     File "<stdin>", line 4, in <module>
   RuntimeError

연쇄 메커니즘에 대한 자세한 내용은, 내장 예외를 참조하십시오.


8.6. 사용자 정의 예외
=====================

새 예외 클래스를 만듦으로써 프로그램은 자신의 예외에 이름을 붙일 수 있
습니다 (파이썬 클래스에 대한 자세한 내용은 클래스 를 보세요). 예외는
보통 직접적으로나 간접적으로 "Exception" 클래스를 계승합니다.

Exception classes can be defined which do anything any other class can
do, but are usually kept simple, often only offering a number of
attributes that allow information about the error to be extracted by
handlers for the exception.

대부분의 예외는 표준 예외들의 이름들과 유사하게, "Error" 로 끝나는 이
름으로 정의됩니다.

많은 표준 모듈들은 그들이 정의하는 함수들에서 발생할 수 있는 그 자신만
의 예외들을 정의합니다. 클래스에 관한 더 자세한 정보는 클래스 장에서
다룹니다.


8.7. 뒷정리 동작 정의하기
=========================

"try" 문은 또 다른 선택적 절을 가질 수 있는데 모든 상황에 실행되어야만
하는 뒷정리 동작을 정의하는 데 사용됩니다. 예를 들어:

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

"finally" 절이 있으면, "try" 문이 완료되기 전에 "finally" 절이 마지막
작업으로 실행됩니다. "finally" 절은 "try" 문이 예외를 생성하는지와 관
계없이 실행됩니다. 다음은 예외가 발생할 때 더 복잡한 경우를 설명합니다
:

* "try" 절을 실행하는 동안 예외가 발생하면, "except" 절에서 예외를 처
  리할 수 있습니다. 예외가 "except" 절에서 처리되지 않으면, "finally"
  절이 실행된 후 예외가 다시 발생합니다.

* "except"나 "else" 절 실행 중에 예외가 발생할 수 있습니다. 다시,
  "finally" 절이 실행된 후 예외가 다시 발생합니다.

* If the "finally" clause executes a "break", "continue" or "return"
  statement, exceptions are not re-raised.

* "try" 문이 "break", "continue" 또는 "return" 문에 도달하면,
  "finally" 절은 "break", "continue" 또는 "return" 문 실행 직전에 실행
  됩니다.

* "finally" 절에 "return" 문이 포함되면, 반환 값은 "try" 절의 "return"
  문이 주는 값이 아니라, "finally" 절의 "return" 문이 주는 값이 됩니다
  .

예를 들면:

   >>> def bool_return():
   ...     try:
   ...         return True
   ...     finally:
   ...         return False
   ...
   >>> bool_return()
   False

더 복잡한 예:

   >>> 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.0
   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 <module>
     File "<stdin>", line 3, in divide
   TypeError: unsupported operand type(s) for /: 'str' and 'str'

보인 바와 같이, "finally" 절은 모든 경우에 실행됩니다. 두 문자열을 나
눠서 발생한 "TypeError" 는 "except" 절에 의해 처리되지 않고 "finally"
절이 실행된 후에 다시 일어납니다.

실제 세상의 응용 프로그램에서, "finally" 절은 외부 자원을 사용할 때,
성공적인지 아닌지와 관계없이, 그 자원을 반납하는 데 유용합니다 (파일이
나 네트워크 연결 같은 것들).


8.8. 미리 정의된 뒷정리 동작들
==============================

어떤 객체들은 객체가 더 필요 없을 때 개입하는 표준 뒷정리 동작을 정의
합니다. 그 객체를 사용하는 연산의 성공 여부와 관계없습니다. 파일을 열
고 그 내용을 화면에 인쇄하려고 하는 다음 예를 보세요.

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

이 코드의 문제점은 이 부분이 실행을 끝낸 뒤에도 예측할 수 없는 기간 동
안 파일을 열린 채로 둔다는 것입니다. 간단한 스크립트에서는 문제가 되지
않지만, 큰 응용 프로그램에서는 문제가 될 수 있습니다. "with" 문은 파일
과 같은 객체들이 즉시 올바르게 뒷정리 되도록 보장하는 방법을 제공합니
다.

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

문장이 실행된 후에, 줄을 처리하는 데 문제가 발생하더라도, 파일 *f* 는
항상 닫힙니다. 파일과 같이, 미리 정의된 뒷정리 동작들을 제공하는 객체
들은 그들의 설명서에서 이 사실을 설명합니다.
