今さらPython3 (32) - エラーハンドリング

今日で第4章が終えられるかな?

入門 Python 3

入門 Python 3

エラーハンドリング try & except

まずは自分で動きを把握しているものに例外ハンドリングを入れて見ましょうというのは正しいアプローチだね。

>>> short_list = [1, 2, 3]
>>> position = 5
>>> short_list[position]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: list index out of range
>>>

0, 1, 2しかないポジションに5を入れたので怒られたパターンですね。

>>> short_list = [1, 2, 3]
>>> position = 5
>>> try:
...     short_list[position]
... except:
...      print('Need a position between 0 and', len(short_list)-1, 'but got', pos
ition)
...
Need a position between 0 and 2 but got 5
>>>

再利用するなら、こんな風に作ればOK?

>>> def get_value(slist, position):
...     try:
...         print(slist[position])
...     except:
...         print('Need a position between 0 and', len(slist)-1, 'but got', posi
tion)
...
>>> short_list
[1, 2, 3]
>>> get_value(short_list, 5)
Need a position between 0 and 2 but got 5
>>> get_value(short_list, 1)
2
>>>

これで同じ例は動くけど、例えばslistに渡したリストが空とか、考えないといけないケースはたくさんあるよね。

>>> get_value([], 1)
Need a position between 0 and -1 but got 1
>>> get_value((1,2,3,4,5), 1)
2
>>> get_value({1:'one',2:'two',3:'three',4:'four',5:'five'}, 1)
one
>>>
>>> get_value([], -1)
Need a position between 0 and -1 but got -1

最後のパターンとか0と-1の間にしろと言われて-1入れて怒られてるし。ちょっと余談が過ぎたけど、起こりうる例外のケースは色々あって、そのタイプごとにハンドリングするような仕組みにしようということ。

>>> short_list = [1,2,3]
>>> while True:
...     value = input('Position [q to quit]? ')
...     if value == 'q':
...         break
...     try:
...         position = int(value)
...         print(short_list[position])
...     except IndexError as err:
...         print('Bad index:', position)
...     except Exception as other:
...         print('Something else broke:', other)
...
Position [q to quit]? 1
2
Position [q to quit]? 0
1
Position [q to quit]? 2
3
Position [q to quit]? 3
Bad index: 3
Position [q to quit]? 2
3
Position [q to quit]? two
Something else broke: invalid literal for int() with base 10: 'two'
Position [q to quit]? q
>>>

ロジック的には分かるんだけど、イマイチ腑に落ちないのは、exceptの後ろに指定する例外タイプを何にするべきかというのがイマイチつかめない。これを参照しろというのは分かるけど。

5. 組み込み例外 — Python 3.4.3 ドキュメント

実際には動かしながらいろいろなエラーを出して、exceptを追加していく感じになるんだろうけど。上の例でinvalid literal for int() with base 10となっているのは例外タイプには見えないんだよね。。。

>>> short_list
[1, 2, 3]
>>> postion = '2.5'
>>> short_list[postion]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: list indices must be integers, not str
>>> postion = 'two'
>>> short_list[postion]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: list indices must be integers, not str
>>> int('two')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: 'two'

あ、でも分かった気がする。これのTypeError, ValueErrorってのがそうだよね。

独自の例外クラス

Pythonじゃない別の言語で、初めてObject Orientedの概念を学んだときのこと。開発ツールでクラスを作ろうとしたら、選べるオプションの中にException classってのがあって、何だこれは?と思ったんだけど、今みたいな前提を分できたなら、あっさり分かりそうな気がする。

本の中では、第6章のクラスをやってから戻ってくればと言っているけど、ここは突き進むしかないでしょw。

>>> words = ['eeenie', 'meenie', 'miny', 'MO']
>>> for word in words:
...     if word.isupper():
...         raise UppercaseException(word)
...
>>> for word in words:
...     if word.isupper():
...         raise UppercaseException(word)
...
Traceback (most recent call last):
  File "<stdin>", line 3, in <module>
__main__.UppercaseException: MO
>>>

isupper()がTrueならraiseで例外上げてくれと言ってるんだね。

>>> try:
...     raise OopsException('panic')
... except OopsException as exc:
...     print(exc)
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
NameError: name 'OopsException' is not defined

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 3, in <module>
NameError: name 'OopsException' is not defined
>>>

でも、最後に載っているこれだけは本に書いてある通りの動きにならないな。

>>> class OopsException(Exception):
...     pass
... 
>>> try:
...     raise OopsException('Caught an Oops')
... except OopsException as exc:
...     print(exc)
... 
Caught an Oops

Exceptionを継承したクラスを前もって定義しないとダメということか。

※練習問題やった後で戻って書いているので、本と例が違います。

(つづく)