Pythonの例外をサクサク理解しよう特集:Visual Studioで始めるPythonプログラミング(3/4 ページ)

» 2017年01月13日 05時00分 公開
[かわさきしんじInsider.NET編集部]

キャッチする例外の指定

 上のコードではexcept節に何も指定していないが、これは「ワイルドカードのexcept節」と呼ばれるもので、例外なら何でもキャッチしてしまう。これはPythonではあまり推奨されておらず、except節でキャッチする例外を個別に指定していき、「ワイルドカードのexcept節」を記述するとしたら最後にする。以下にexcept節にキャッチする例外を指定した例を示す(「ワイルドカードのexcept節」がないとどうなるか見るため、ここではあえてこれを指定していない)。

def func(exp):
  try:
    print(eval(exp))
  except ZeroDivisionError:
    print('division by zero')
  except NameError:
    print('undefined name')


キャッチする例外を指定

 関数funcに文字列を渡すと、それが評価される。実際に実行した例を以下に示す。ここでは「1/0」のゼロ除算、「undefinedvar」という未定義の名前に2を加算、数値の「1」に文字列の「100」を加算の3つの計算を行っている。

>>> def func(exp):
...   try:
...     print(eval(exp))
...   except ZeroDivisionError:
...     print('division by zero')
...   except NameError:
...     print('undefined name')
...
>>> func('1/0')
division by zero
>>> func('undefinedvar + 2')
undefined name
>>> func('1 + str(100)')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in func
  File "<string>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'str'


「ワイルドカードのexcept節」がないので、異なる型の値の加算はキャッチされなかった

 最初の2つに関してはexcept節で例外を指定しているのでキャッチされるが、最後のものについては指定がないので、処理されずにPythonがスタックトレースと発生した例外を表示している。「ワイルドカードのexcept節」があればこうした例外もキャッチできる(が、意図せずに発生した例外の原因がプログラムミスにあるとすれば、何でもかんでも「ワイルドカードのexcept節」でキャッチしてしまうのがよいかには議論の余地があるだろう)。

 このとき注意したいのは、例外クラスを指定する順番だ。例えば、上のコードを次のように修正したとする。

def func(exp):
  try:
    print(eval(exp))
  except Exception:
    print('some exception!')
  except ZeroDivisionError:
    print('division by zero')
  except NameError:
    print('undefined name')


キャッチする例外を指定する際には、その順番に注意する必要がある

 ここではExceptionクラスを真っ先に指定しているが、こうするとZeroDivisionError例外が発生しようが、NameError例外が発生しようが、最初のexcept節でキャッチされてしまう(実行例は割愛)。これは2つの例外クラスがExceptionクラスの派生クラスとなっているからだ(これら2つの例外クラスがExceptionクラスと「互換性がある」あるいは「is-aの関係」にあるため)。

 また、例外を指定した場合には、例外クラスに続けて「as 変数名」とすることで、その例外を表すオブジェクトを変数に代入できる。以下に例を示す。ここでは上の関数func呼び出しで指定した引数を要素としたリストに対して反復処理を行って、その評価を行っている。

for item in ['1/0', 'hoge + 2', '1 + str(100)']:
  try:
    print(eval(item))
  except ZeroDivisionError as e:
    print('devision by zero:', e)
  except NameError as e:
    print('undefined name:', e.args)
  except TypeError as e:
    print('incompatible types:', e, e.args)


例外オブジェクトを取得する

 このコードでは「as e」として変数eに例外オブジェクトを取得している。例外オブジェクトのメンバ「args」には通常、エラーメッセージが格納されている。そのため「e.args」とすればエラーメッセージにアクセスできるが、単に「e」とするだけでもこれを取得できるように準備されている。その違いが分かるように、3つのexcept節にある関数print呼び出しではそれぞれ「e」のみ、「e.args」のみ、その両方を出力するようにしてある。実行結果は次のようになる。

>>> for item in ['1/0', 'hoge + 2', '1 + str(100)']:
...   try:
...     print(eval(item))
...   except ZeroDivisionError as e:
...     print('devision by zero:', e)
...   except NameError as e:
...     print('undefined name:', e.args)
...   except TypeError as e:
...     print('incompatible types:', e, e.args)
...
devision by zero: division by zero
undefined name: ("name 'hoge' is not defined",)
incompatible types: unsupported operand type(s) for +: 'int' and 'str'
   ("unsupported operand type(s) for +: 'int' and 'str'",)


実行結果
出力結果が4行あるように見えるが、これは3つ目の出力結果に改行を挿入したため。

 「e.args」ではエラーメッセージがかっこに囲まれて出力されているが、その内容は「e」と同じことが分かる。

else節

 else節についても簡単に触れておこう。Pythonではwhile文などでもelse節を記述できたが、try文でもelse節を書ける。else節はtry節で例外が発生しなかった場合に実行される(else節とは異なり、finally節は例外の有無に関係なく実行される)。

 先ほどの関数funcを次のように変更して、その動作を確認してみよう。

def func(exp):
  try:
    print(eval(exp))
  except ZeroDivisionError:
    print('division by zero')
  except NameError:
    print('undefined name')
  except Exception:
    print('some exception!')
  else:
    print('success!')
  finally:
    print('finally clause')


関数funcにelse節とfinally節を追加

 実行すると次のようになる。エラーなしでtry文が実行されたときには、以下で強調書体としている「success!」から分かるようにelse節が実行される。

>>> def func(exp):
...   try:
...     print(eval(exp))
...   except ZeroDivisionError:
...     print('division by zero')
...   except NameError:
...     print('undefined name')
...   except Exception:
...     print('some exception!')
...   else:
...     print('success!')
...   finally:
...     print('finally clause')
...
>>> func('1 + 1'# 成功する場合
2
success!           # else節が実行された
finally clause     # finally節が実行される
>>> func('1 / 0'# 失敗する場合
division by zero
finally clause     # else節は実行されずにfinally節が実行される


例外が発生しなければelse節も実行される

 最後に例外の送出についても簡単にまとめておこう。

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。