본문 바로가기

파이썬 튜토리얼 / Pythonチュートリアル 8. 에러와 예외 エラーと例外

https://docs.python.org/ko/3/tutorial/errors.html


8. 에러와 예외 エラーと例外

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


これまでエラーメッセージについては簡単に触れるだけでしたが、チュートリアル中の例を自分で試していたら、実際にいくつかのエラーメッセージを見ていることでしょう。エラーには (少なくとも) 二つのはっきり異なる種類があります。それは 構文エラー (syntax error) と 例外 (exception) です。


8.1. 문법 에러 構文エラー

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


構文エラーは構文解析エラー (parsing error) としても知られており、Python を勉強している間に最もよく遭遇する問題の一つでしょう:


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

파서는 문제가 되는 줄을 다시 보여주고 줄에서 에러가 감지된 가장 앞의 위치를 가리키는 작은 '화살표'를 표시합니다. 에러는 화살표 앞에 오는 토큰이 원인입니다 (또는 적어도 그곳에서 감지되었습니다): 


이 예에서, 에러는 함수 print() 에서 감지되었는데, 그 앞에 콜론 (':') 이 빠져있기 때문입니다. 파일 이름과 줄 번호가 인쇄되어서, 입력이 스크립트로부터 올 때 찾을 수 있도록 합니다.


パーサは違反の起きている行を表示し、小さな「矢印」を表示して、行中でエラーが検出された最初の位置を示します。エラーは矢印の 直前の トークンでひき起こされています (または、少なくともそこで検出されています)。


上記の例では、エラーは関数 print() で検出されています。コロン (':') がその前に無いからです。入力がスクリプトから来ている場合は、どこを見ればよいか分かるようにファイル名と行番号が出力されます。


8.2. 예외 例外

문장이나 표현식이 문법적으로 올바르다 할지라도, 실행하려고 하면 에러를 일으킬 수 있습니다. 

실행 중에 감지되는 에러들을 예외 라고 부르고 무조건 치명적이지는 않습니다: 


たとえ文や式が構文的に正しくても、実行しようとしたときにエラーが発生するかもしれません。

実行中に検出されたエラーは 例外 (exception) と呼ばれ、常に致命的とは限りません。


파이썬 프로그램에서 이것들을 어떻게 다루는지 곧 배우게 됩니다. 하지만 대부분의 예외는 프로그램이 처리하지 않아서, 여기에서 볼 수 있듯이 에러 메시지를 만듭니다:


これから、Python プログラムで例外をどのように扱うかを学んでいきます。ほとんどの例外はプログラムで処理されず、以下に示されるようなメッセージになります:


>>>
>>> 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't convert 'int' object to str implicitly

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


エラーメッセージの最終行は何が起こったかを示しています。例外は様々な型 (type) で起こり、その型がエラーメッセージの一部として出力されます。上の例での型は ZeroDivisionErrorNameErrorTypeError です。例外型として出力される文字列は、発生した例外の組み込み名です。これは全ての組み込み例外について成り立ちますが、ユーザ定義の例外では (成り立つようにするのは有意義な慣習ですが) 必ずしも成り立ちません。標準例外の名前は組み込みの識別子です (予約語ではありません)。



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


残りの行は例外の詳細で、その例外の型と何が起きたかに依存します。



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

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


エラーメッセージの先頭部分では、例外が発生した実行コンテキスト (context) を、スタックのトレースバック (stack traceback) の形式で示しています。一般には、この部分にはソースコード行をリストしたトレースバックが表示されます。しかし、標準入力から読み取られたコードは表示されません。


組み込み例外 には、組み込み例外とその意味がリストされています。


8.3. 예외 처리하기 例外を処理する


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


例外を選別して処理するようなプログラムを書くことができます。以下の例を見てください。この例では、有効な文字列が入力されるまでユーザに入力を促しますが、ユーザがプログラムに (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 절 (try 와 except 사이의 문장들) 이 실행됩니다.
  • 예외가 발생하지 않으면, except 절 을 건너뛰고 try 문의 실행은 종료됩니다.
  • try 절을 실행하는 동안 예외가 발생하면, 절의 남은 부분들을 건너뜁니다. 그런 다음 형이 except 키워드 뒤에 오는 예외 이름과 매치되면, 그 except 절이 실행되고, 그런 다음 실행은 try 문 뒤로 이어집니다.
  • except 절에 있는 예외 이름들과 매치되지 않는 예외가 발생하면, 외부에 있는 try 문으로 전달됩니다; 처리기가 발견되지 않으면, 처리되지 않은 예외 이고 위에서 보인 것과 같은 메시지를 출력하면서 실행이 멈춥니다.
  • まず、 try 節 (try clause) (キーワード try と except の間の文) が実行されます。
  • 何も例外が発生しなければ、 except 節 をスキップして try 文の実行を終えます。
  • try 節内の実行中に例外が発生すると、その節の残りは飛ばされます。次に、例外型が except キーワードの後に指定されている例外に一致する場合、except 節が実行された後、 try 文の後ろへ実行が継続されます。
  • もしも except 節で指定された例外と一致しない例外が発生すると、その例外は try 文の外側に渡されます。例外に対するハンドラ (handler、処理部) がどこにもなければ、 処理されない例外 (unhandled exception) となり、上記に示したようなメッセージを出して実行を停止します。

각기 다른 예외에 대한 처리기를 지정하기 위해, try 문은 하나 이상의 except 절을 가질 수 있습니다. 최대 하나의 처리기가 실행됩니다. 처리기는 해당하는 try 절에서 발생한 예외만 처리할 뿐 같은 try 문의 다른 처리기가 일으킨 예외를 처리하지는 않습니다. except 절은 괄호가 있는 튜플로 여러 개의 예외를 지정할 수 있습니다, 예를 들어:


一つの try 文には複数の except 節が付けられ、別々の例外に対するハンドラを指定できます。 多くとも一つのハンドラしか実行されません。 ハンドラは対応する try 節内で発生した例外だけを処理し、同じ try 節内の別の例外ハンドラで起きた例外は処理しません。 except 節では丸括弧で囲ったタプルという形で複数の例外を指定できます。例えば次のようにします:


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

except 절에 있는 클래스는 예외와 같은 클래스이거나 베이스 클래스일 때 매치됩니다 (하지만 다른 방식으로는 매치되지 않습니다 --- 자식 클래스를 나열한 except 절은 베이스 클래스와 매치되지 않습니다). 예를 들어, 다음과 같은 코드는 B, C, D를 그 순서대로 인쇄합니다:


except 節のクラスは、例外と同じクラスか基底クラスのときに互換 (compatible)となります。 (逆方向では成り立ちません --- 派生クラスの例外がリストされている except 節は基底クラスの例外と互換ではありません)。例えば、次のコードは、 B, C, D を順序通りに出力します:


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")

except 절이 뒤집히면 (except B 가 처음에 오도록), B, B, B를 인쇄하게 됨에 주의하세요 --- 처음으로 매치되는 절이 실행됩니다.


except 節が逆に並んでいた場合 (except B が最初にくる場合)、 B, B, B と出力されるはずだったことに注意してください --- 最初に一致した except 節が駆動されるのです。


마지막 except 절은 예외 이름을 생략할 수 있는데, 와일드카드 역할을 합니다. 이것을 사용할 때는 극도의 주의를 필요로 합니다. 이런 식으로 실제 프로그래밍 에러를 가리기 쉽기 때문입니다! 에러 메시지를 인쇄한 후에 예외를 다시 일으키는데 사용될 수도 있습니다 (호출자도 예외를 처리할 수 있도록):


最後の except 節では例外名を省いて、ワイルドカード (wildcard、総称記号) にすることができます。ワイルドカードの except 節は非常に注意して使ってください。というのは、ワイルドカードは通常のプログラムエラーをたやすく隠してしまうからです!ワイルドカードの except 節はエラーメッセージを出力した後に例外を再送出する (関数やメソッドの呼び出し側が同様にして例外を処理できるようにする) 用途にも使えます:



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:
    print("Unexpected error:", sys.exc_info()[0])
    raise

try ... except 문은 선택적인 else 절 을 갖는데, 있다면 모든 except 절 뒤에와야 합니다. try 절이 예외를 일으키지 않을 때 실행되어야만 하는 코드에 유용합니다. 예를 들어:


try ... except 文には、オプションで else 節 (else clause) を設けることができます。 else 節を設ける場合、全ての except 節よりも後ろに置かなければなりません。 else 節は try 節で全く例外が送出されなかったときに実行されるコードを書くのに役立ちます。例えば次のようにします:


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 문에 의해 보호되고 있는 코드가 일으키지 않은 예외를 우연히 잡게 되는 것을 방지하기 때문입니다.


追加のコードを付け加えるのは try 節よりも else 節の方がよいでしょう。 なぜなら、そうすることで try ... except 文で保護されたコードから送出されたもの以外の例外を過って捕捉してしまうという事態を避けられるからです。


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


例外が発生するとき、例外は関連付けられた値を持つことができます。この値は例外の 引数 (argument) とも呼ばれます。引数の有無および引数の型は、例外の型に依存します。


except 절은 예외 이름 뒤에 변수를 지정할 수 있습니다. 변수는 인자들이 instance.args 에 저장된 예외 인스턴스에 연결됩니다. 편의를 위해, 예외 인스턴스는 __str__() 를 정의해서, .args를 참조하지 않고도 인자들을 직접 인쇄할 수 있습니다. 예외를 일으키기 전에 인스턴스를 먼저 만들고 필요한 어트리뷰트들을 추가할 수도 있습니다.


except 節では、例外名の後に変数を指定することができます。この変数は例外インスタンスに結び付けられており、 instance.args に例外インスタンス生成時の引数が入っています。例外インスタンスには __str__() が定義されており、 .args を参照しなくても引数を直接印字できるように利便性が図られています。必要なら、例外を送出する前にインスタンス化して、任意の属性を追加できます。



>>>
>>> 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

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

例外が引数を持っていれば、それらは処理されない例外のメッセージの最後の部分 (「詳細説明」) に出力されます。


예외 처리기는 단지 try 절에 직접 등장하는 예외뿐만 아니라, try 절에서 (간접적으로라도) 호출되는 내부 함수들에서 발생하는 예외들도 처리합니다. 예를 들어:

例外ハンドラは、try 節の直接内側で発生した例外を処理するだけではなく、その try 節から (たとえ間接的にでも) 呼び出された関数の内部で発生した例外も処理します。例えば:


>>>
>>> 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 文を使って、特定の例外を発生させることができます。例えば:

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

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


raise の唯一の引数は送出される例外を指し示します。 これは例外インスタンスか例外クラス (Exception を継承したクラス) でなければなりません。 例外クラスが渡された場合は、引数無しのコンストラクタが呼び出され、暗黙的にインスタンス化されます:


raise ValueError  # shorthand for 'raise ValueError()'

만약 예외가 발생했는지는 알아야 하지만 처리하고 싶지는 않다면, 더 간단한 형태의 raise 문이 그 예외를 다시 일으킬 수 있게 합니다:


例外が発生したかどうかを判定したいだけで、その例外を処理するつもりがなければ、単純な形式の 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. 사용자 정의 예외 ユーザー定義例外

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


プログラム上で新しい例外クラスを作成することで、独自の例外を指定することができます (Python のクラスについては クラス 参照)。例外は、典型的に Exception クラスから、直接または間接的に派生したものです。


예외 클래스는 다른 클래스들이 할 수 있는 어떤 것도 가능하도록 정의될 수 있지만, 보통은 간단하게 유지합니다. 

종종 예외 처리기가 에러에 관한 정보를 추출할 수 있도록 하기 위한 몇 가지 어트리뷰트들을 제공하기만 합니다. 

여러 가지 서로 다른 에러들을 일으킬 수 있는 모듈을 만들 때, 흔히 사용되는 방식은 모듈에서 정의되는 예외들의 베이스 클래스를 정의한 후, 각기 다른 에러 조건마다 특정한 예외 클래스를 서브 클래스로 만드는 것입니다:


例外クラスでは、普通のクラスができることなら何でも定義することができますが、通常は単純なものにしておきます。大抵は、いくつかの属性だけを提供し、例外が発生したときにハンドラがエラーに関する情報を取り出せるようにする程度にとどめます。複数の別個の例外を送出するようなモジュールを作成する際には、そのモジュールで定義されている例外の基底クラスを作成するのが一般的なプラクティスです:

class Error(Exception):
    """Base class for exceptions in this module."""
    pass

class InputError(Error):
    """Exception raised for errors in the input.

    Attributes:
        expression -- input expression in which the error occurred
        message -- explanation of the error
    """

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

class TransitionError(Error):
    """Raised when an operation attempts a state transition that's not
    allowed.

    Attributes:
        previous -- state at beginning of transition
        next -- attempted new state
        message -- explanation of why the specific transition is not allowed
    """

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

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

ほとんどの例外は、標準の例外の名前付けと同様に、"Error" で終わる名前で定義されています。


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

多くの標準モジュールでは、モジュールで定義されている関数内で発生する可能性のあるエラーを報告させるために、独自の例外を定義しています。クラスについての詳細な情報は クラス 章で提供されています。


8.6. 뒷정리 동작 정의하기 クリーンアップ動作を定義する

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

try 文にはもう一つオプションの節があります。この節はクリーンアップ動作を定義するためのもので、どんな状況でも必ず実行されます。例を示します:


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

finally 절 은 예외의 발생 여부와 관계없이 try 문을 떠날 때 항상 실행됩니다. try 절에서 예외가 발생하고 except 절에서 처리되지 않으면 (또는 except 나 else 절에서 발생하면), finally절이 실행된 후에 다시 일으킵니다. finally 절은 try 문의 다른 모든 절에서 breakcontinuereturn 문에 의해 "빠져나가는 길에" 도 실행됩니다. 더 복잡한 예는 이렇습니다:


finally 節 (finally clause) は、例外が発生したかどうかに関わらず、 try 文を抜ける前に常に実行されます。 try 節の中で例外が発生して、 except 節で処理されていない場合 (つまり except 節か else 節で例外が発生した場合) は、 finally 節を実行した後、その例外を再送出します。 try 節から break 文や continue 文、 return 文経由で抜ける際にも、 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.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 節はどの場合にも実行されています。 文字列で割り算をすることで発生した TypeError は except 節で処理されていないので、 finally 節実行後に再度送出されています。


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


実世界のアプリケーションでは、 finally 節は(ファイルやネットワーク接続などの)外部リソースを、利用が成功したかどうかにかかわらず解放するために便利です。


8.7. 미리 정의된 뒷정리 동작들 定義済みクリーンアップ処理

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


オブジェクトのなかには、その利用の成否にかかわらず、不要になった際に実行される標準的なクリーンアップ処理が定義されているものがあります。以下の、ファイルをオープンして内容を画面に表示する例をみてください。


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

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


このコードの問題点は、コードの実行が終わった後に不定の時間ファイルを開いたままでいることです。

これは単純なスクリプトでは問題になりませんが、大きなアプリケーションでは問題になりえます。 with 文はファイルのようなオブジェクトが常に、即座に正しくクリーンアップされることを保証します。

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

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


この文が実行されたあとで、たとえ行の処理中に問題があったとしても、ファイル f は常に close されます。ファイルなどの、定義済みクリーンアップ処理を持つオブジェクトについては、それぞれのドキュメントで示されます。