初めてのPython(27章)
例外に関連するステートメントは4つ
- try/except/else/finally
- raise
- assert
- with/as
try/except/else/finally
発生した例外を検知/処理するためのステートメント
構文とそれぞれのブロックの意味
try: # 例外が発生する可能性のあるステートメント <statement> except [例外名[, データ]] # <例外名>の例外が発生した時に実行したいステートメント # <例外名>および<データ>は省略可能 <statement> else: # 例外が発生しなかった時に実行したいステートメント <statement> finally: # 例外の発生有無にかかわらず実行したいステートメント <statement
except節は複数定義可能。<例外名>および<データ>は1つのexceptステートメントに対して複数定義可能
Yasuyuki-KOBAYASHI-no-MacBook-Pro:~ y_kobayashi$ cat exec.data [kobakoba0723@fedora13-intel64 ~]$ python Python 2.6.4 (r264:75706, Apr 1 2010, 02:55:51) [GCC 4.4.3 20100226 (Red Hat 4.4.3-8)] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> try: ... raise IndexError, ('hoge', 'piyo') ... except (IndexError, TypeError), (data1, data2): ... print type(data1), type(data2) ... print data1, data2 ... <type 'str'> <type 'str'> hoge piyo >>> try: ... raise TypeError, ('hoge', 'piyo') ... except (IndexError, TypeError), (data1, data2): ... print data1, data2 ... hoge piyo >>>
あと、色々と遊んでたら不思議なことが起こった。
SyntaxErrorを発生させたときの<データ>の値がなんか変@タプルの形でデータを渡す
>>> try: ... raise SyntaxError, ('hoge', 'piyo') ... except SyntaxError, data: ... print data ... hoge (p) >>> >>> try: ... raise SyntaxError, ['hoge', 'piyo'] ... except SyntaxError, data: ... print data ... ['hoge', 'piyo']
MacのPython(2.6.1)でもおんなじ結果になった.
リファレンスでraise文を調べてみたら、
最初のオブジェクトがクラスの場合、例外の型になります。
第二のオブジェクトは、例外の値を決めるために使われます:
第二のオブジェクトがタプルの場合、クラスのコンストラクタに対する引数リストとして使われます。
このようにしてコンストラクタを呼び出して生成したインスタンスが例外の値になります。
とあった。
てことは、
>>> print SyntaxError(('hoge','piyo')) ('hoge', 'piyo') >>>
あら、'hoge (p)'ってならない。。。なんか勘違いしてんのかなぁ。。。
raise
例外を発生させるためのステートメントで次のようにして使う
raise raise <name> raise <name>, <data>
- ビルドイン例外
- ユーザ定義例外(Exceptionを継承したクラスおよびそのインスタンス)
本には、文字列を代入した変数が指定できるとあったが、2.6では駄目みたい。
>>> hoge = "hoge" >>> raise hoge Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: exceptions must be classes or instances, not str >>>
assert
特定条件を満たす時に例外を発生させるためのステートメント。
デバッグなどプログラム開発段階で使用がメイン。
最適化するためにコンパイルするとコードが消えてしまうらしい。
機能自体は、ifステートメント + raiseステートメントで代用可能。
asssert <test>, <data> ↑は ↓と同じ if __debug__: if not <test>: raise AssertionError, <data>
__debug__の値は、
現在の実装では、組み込み変数 __debug__ は通常の状況では True であり、
最適化がリクエストされた場合(コマンドラインオプション -O)は False 。
−O をつけると常に if の結果が False で実行されないから、そもそもコード自体を出力しないってことなのかな。
with/as
try/except/finally同様にのこと + 前処理が実現出来る。
with と コンテキストマネージャを利用することで、上記のことが出来る
コンテキストマネージャは、以下の2つの特殊メソッドを実装したクラスのインスタンス。
- __enter__
- __exit__
with expression [as target] : suite
上記は次のように解釈され実行される
- expressionが実行されコンテキストマネージャが取得される
- コンテキストマネージャの__enter__()が実行される
- as 以降がある場合は、__enter__()の復帰値がtargetに代入される
- suite が実行される
- コンテキストマネージャの__exit__()が実行される
suite の実行中に例外が発生した場合、例外が__exit__に引数として渡される。
__exit__には、except と finallyブロックの役割をさせることが出来る。
kobakoba0723@fedora13-intel64 with_as]$ cat with_as.py #!/usr/bin/env python class with_as(object): def __enter__(self): print "enter __enter__()" def __exit__(self, exec_type, exec_val, exec_tb): if exec_type == TypeError: print "Catch TypeError" print "enter __exit__()" return True print "enter __exit__()" return False with with_as(): print "enter block" raise TypeError [kobakoba0723@fedora13-intel64 with_as]$
上と下のコードは等価と考えることが出来る
[kobakoba0723@fedora13-intel64 with_as]$ cat try_except_finally.py #!/usr/bin/env python print 'enter __enter__' try: print "enter block" raise TypeError except TypeError: print "Catch TypeError" finally: print "enter __exit__" [kobakoba0723@fedora13-intel64 with_as]$
実行結果も同じになる。
[kobakoba0723@fedora13-intel64 with_as]$ python with_as.py enter __enter__() enter block Catch TypeError enter __exit__() [kobakoba0723@fedora13-intel64 with_as]$ python try_except_finally.py enter __enter__ enter block Catch TypeError enter __exit__ [kobakoba0723@fedora13-intel64 with_as]$
でもって、__exit__の中で定義していない例外を上げてやると、
exceptに指定しなかった例外が発生したとき同様に例外が表に現れる。
[kobakoba0723@fedora13-intel64 with_as]$ python with_as.py enter __enter__() enter block enter __exit__() Traceback (most recent call last): File "with_as.py", line 18, in <module> raise IndexError IndexError [kobakoba0723@fedora13-intel64 with_as]$
__exit__の外に例外を出すか出さないかは、__exit__の復帰値で制御する。
Trueの場合は外に出さない、Falseの場合は外に出す。
with/as を使うとコードが少なくて済むのがファイルのアクセス。
try/except/finallyを使う場合だと以下のようになるが、
try: f = open(file_name, mode) for line in f: print line.strip() except IOError: print "file can not open" finally: f.close()
with/asを使うとファイルのオープン/クローズを自動でやってくれるので、これだけで済んじゃう。
with open(file_name, mode) as f: for line in f: print line.strip()
ということで早速確認
[kobakoba0723@fedora13-intel64 with_as]$ cat open_file.py #!/usr/bin/env python with open('hoge.data', 'r') as f: for line in f: print line.strip() print f [kobakoba0723@fedora13-intel64 with_as]$ ls hoge.data open_file.py [kobakoba0723@fedora13-intel64 with_as]$ python open_file.py hoge piyo egg spam <closed file 'hoge.data', mode 'r' at 0x7f05cd240a48> [kobakoba0723@fedora13-intel64 with_as]$ mv hoge.data piyo.data [kobakoba0723@fedora13-intel64 with_as]$ python open_file.py Traceback (most recent call last): File "open_file.py", line 3, in <module> with open('hoge.data', 'r') as f: IOError: [Errno 2] No such file or directory: 'hoge.data' [kobakoba0723@fedora13-intel64 with_as]$
うん、自動的にファイルの開け閉めしてくれて、エラー処理もしてくれてる。
で、ファイルが無かったときにプログラムを止めないようにするには、
このモジュールの外側でIOErrorを拾ってやればよいってことになる。
以下のようなコンテキストマネージャを返すクラスを作って、
__exit__でIOErrorを受け取れば、モジュールの外側でごにょごにょしなくていいんじゃない?
なんておもったけどだめでした。そんなにあまかぁない。
[kobakoba0723@fedora13-intel64 with_as]$ cat open_file_myself.py #!/usr/bin/env python class openFile(object): def __init__(self, file_name, file_attr): self.file = file_name self.attr = file_attr def __enter__(self): self.fd = open(self.file, self.attr) return self.fd def __exit__(self, exec_type, exec_val, exec_tb): retval = False if exec_type == None: retval = True elif exec_type == IOError: print 'file cannot open' retval = True self.fd.close() return retval with openFile('hoge.data', 'r') as f: for line in f: print line.strip() print f [kobakoba0723@fedora13-intel64 with_as]$ ls hoge.data open_file_myself.py [kobakoba0723@fedora13-intel64 with_as]$ python open_file_myself.py hoge piyo egg spam <closed file 'hoge.data', mode 'r' at 0x7f25afea4a48> [kobakoba0723@fedora13-intel64 with_as]$ mv hoge.data piyo.data [kobakoba0723@fedora13-intel64 with_as]$ python open_file_myself.py Traceback (most recent call last): File "open_file_myself.py", line 22, in <module> with openFile('hoge.data', 'r') as f: File "open_file_myself.py", line 9, in __enter__ self.fd = open(self.file, self.attr) IOError: [Errno 2] No such file or directory: 'hoge.data' [kobakoba0723@fedora13-intel64 with_as]$
正常系の場合はファイルの開け閉めしてくれていいんだけど、エラー系は思った通りに動かない。
というよりもそもそも無理なことをしようとしてたみたい。
ドキュメントによると、
with 文は、 __enter__() メソッドがエラーなく終了した場合には __exit__() が常に呼ばれることを保証します。
もしエラーがターゲットリストへの代入中にエラーが発生した場合には、これはそのスイートの中で発生したエラーと同じように扱われます。
ってことで、__enter__()でエラーが出た場合は、__exit__()が呼ばれる保証はどこにもない。
なので__exit__()でIOErrorを拾ってやろうとしても拾えるもんじゃない。
__exit__()で拾えるのは、
それ以外の時は、外側に例外が出てしまう。
まぁ、ファイルオープンが出来ない状態でプログラムを続ける用途がどれぐらいあるかって考えたら、
この状態でも問題はない気がしてきた。