「Python コード添削道場」を添削する


どう書く?.org登場のきっかけとなったプレゼンで、内容はるびまの「あなたの Ruby コードを添削します」の Python 版。初心者にぜひ見てほしいプレゼンなのだが、残念ながら駆け足でのプレゼンなので、初心者ではわからないかも。ぜひ Web ページに書き起こしてほしいな。


それはそうと、内容で間違っていることがあったので指摘しておきたい。37 分あたりで、次のようなくだりがあった。

これはどのようなコードかといいますと、期待した型と違ったら例外を投げるという、そういうコードです。まあ list なのか整数なのかチェックして、違ってたら例外を投げるというコードなんですけども、...(省略)... この「else: raise Exception()」は、僕だったらこのコードは assert に変えるかなー。assert 文というのは要するに、こうかな? と思ったことが違ってたら例外を投げるというそれ専用のわざわざ文があるわけなので、それにピッタリはまるところでピッタリはまる文をつかったほうがいい ...(省略)... 「条件を満たさなかったら例外を投げる」というのはわざわざ assert 文があるのでこっちを使う方がいいでしょう。


変更前と後のコードは次の通り。

## before
def check_int_or_list(li,func):
    for v in li:
        if isinstance(v,list):
	    check_int_or_list(v,func)
	elif isinstance(v, int):
	    func(v)
	else:
	    raise Exception()

## before
def check_int_or_list(li,func):
    for v in li:
        if isinstance(v,list):
	    check_int_or_list(v,func)
	else:
	    assert isinstance(v, int)
	    func(v)


はっきり言おう。ここで assert 文を使うのは間違っている。この場合なら、変更前のコードが正しい。

assert 文は「条件を満たさなかったら例外を投げる」ためにあるのではない。assert は「かならず条件を満たすことを保証する」ためにあるのだ。データの検証 (validation) のために使うべきではない。

上の例だと、v は list や int だけでなく、他の任意のデータ型である可能性がある。そのような場合にデータが int かどうかを判定するには、フツーに isinstance() で調べるべきである。assert を使うとしたら、「v は絶対に int であるはず。int でない場合はバグである!」と断言できる場合である。つまり、「絶対に int である」ことを表明するために assert を使うのであって、「int かどうかを調べる」ために assert を使ってはいけない。

また assert によるチェックは、コンパイルオプションで on/off できる*1。これはどういうことかというと、assert を off にしてもプログラムの挙動が変わってはいけないということである。これは assert の目的が「かならず条件を満たすことを保証する」ことであることを考えると、当たり前のことだ。しかし上の変更後のコードのように assert をデータ型の検証に使っていた場合、assert を off にするとプログラムの挙動が変わってしまう。

C 言語による開発経験があれば、開発時には assert を on にしておき、出荷時には off にするというのはフツーによくやることだ。そのため、assert を off にしても挙動が変わらないようにするのはごく当たり前のこととして認識されている。しかし Python では assert が on になっているのが普通で、わざわざ assert を off にしてコンパイルすることはしないので、このような誤解が生まれたのだろう。

そもそも、assert により発生する例外はすべて AssertionError である。例外は、なぜ例外が発生したのかを表すために適切な例外クラスを指定して発生すべきである。たとえば型が正しくないのであれば TypeError を指定すべきだし、不適切な引数であれば ValueError が妥当だ。それをせずに、すべてが AssertionError になるのはいただけない。

この添削道場では、raise Exception() のかわりに assert を使っているが、それ自体が間違いである。添削するなら、Exception() ではなく ValueError() を使うよう指摘するのが正しい。

*1:これは質疑応答でも指摘されていた。