私は、Python で次のようなコードを書くことがある。
import datetime def validate_date_string(s): try: assert len(s) == 8 datetime.date(int(s[:4]), int(s[4:6]), int(s[6:])) return True except StandardError: return False def test(): assert validate_date_string('20120229') # leap year assert not validate_date_string('20130229') # not leap year
StandardError を捕捉するのは、google 先生の教えによる。
- Never use catch-all except: statements, or catch Exception or StandardError, unless you are re-raising the exception or in the outermost block in your thread (and printing an error message). Python is very tolerant in this regard and except: will really catch everything including misspelled names, sys.exit() calls, Ctrl+C interrupts, unittest failures and all kinds of other exceptions that you simply don't want to catch.
しかしここには、Python バージョンによって例外クラスの階層が異なる、という重要な情報が抜けている。
例えば Python 2.4 以前は KeyboardInterruput が StarndardError のサブクラスになっているため、上記のコードで try ブロック中に Ctrl-C を受けると意図通りに動かなくなる。具体的には、Ctrl-C で終了できず、無条件に False が返ることになる。
Exception +-- SystemExit +-- StopIteration +-- StandardError | +-- KeyboardInterrupt <snip>
BaseException +-- SystemExit +-- KeyboardInterrupt +-- Exception +-- GeneratorExit +-- StopIteration +-- StandardError <snip>
Python 2.4 以前でも動くようにするためには、StandardError を処理する前に、KeyboardInterruput を明示的に処理する必要がある。
def validate_date_string(s): try: assert len(s) == 8 datetime.date(int(s[:4]), int(s[4:6]), int(s[6:])) return True except KeyboardInterrupt: raise except StandardError: return False
Python は保守的な言語だと思っていたので、まさかこんな所に罠が有るとは思わなかった。確かに KeyboardInterrupt がエラー扱い(StandardError)なのは不自然であるし、言語の仕様的には正しい修正だとは思うが、正直文句の一つくらいも言いたくなる。
これでまた Python が一つ嫌いになった:-p。