ユーニックス総合研究所

  • home
  • archives
  • python-assert

Pythonのassert文でプログラムを念入りに検証する

  • 作成日: 2021-01-14
  • 更新日: 2023-12-24
  • カテゴリ: Python

Pythonのassert文で検証をする

便利な機能と言うものはあらかじめ言語機能に備え付けられていることが多いものです。
それは標準ライブラリだったり組み込み関数だったりします。

そしてもっとも言語機能に近いものが「文」です。
「~文」と言う形で利用頻度の高い機能が言語に実装されます。Pythonも例外ではありません。

assert文もその機能のうちの一つです。
言語機能レベルにまで組み込まれたこの文は、プログラムの状態を検証するのに使われます。
これはC言語由来の文で、使いこなせるようになると非常にパワーを発揮します。

この記事ではこのassert文について解説します。
具体的には↓を見ていきます。

  • assert文とは?
  • assert文の構造
  • assert文のデバッグモード
  • assert文の使用例
  • assert文の使いどころ

assert文とは?

assert文は引数がFalseだった場合に例外AssertionErrorを発生させる組み込み文です。
関数ではありません。
assert文はプログラムの状態をデバッグレベルで検証するときに使われる文です。
このassert文はプログラムがデバッグモードの時に有効になり、デバッグモードが無効になったら無効になります。

assert文は例外AssertionErrorを発生させますが、ただ単純にif文で例外を発生させるのと比べて何が違うかと言うと、先述のようにデバッグモードの状態で文が有効になったり無効になったりする点です。
つまりassert文で書かれたデータの検証の処理は、プログラムを本番モードで稼働させている時は、プログラムから取り払われるわけです。
このような仕組みを持たせることで、開発時のassert文を使ったデバッグが容易になります。

注意したいのは、assert文は本番モード時のプログラムでは無効になるわけですから、本番モードでも検証させたい処理には使ってはいけないということです。
つまりユーザーがログインしているかどうかのチェックとか、引数が有効であるかどうかのチェックなどには使えません。
それらのチェックは本番モードのプログラムでも処理として走らせておく必要があります。仮にこれらの処理をassert文でやってしまうと、本番モード時にはassert文は取り払われるわけですから、これらのチェックが素通りになってしまいます。

assert文とはあくまでも「これが通るのは当たり前」という状況で使うべき文であって、プログラムのクリティカルなロジックを組み立てるものではありません。
「プログラムが正常に動けばこれが通るのは当たり前だけど、念のためassertでチェックしておくか」というのが開発者がassert文を使う時の心理です。
このようにassert文はいわゆる「念入りな」検証をするのに使われます。それはデバッグレベルで行われ、プログラムのクリティカルなロジックに関与しません。

assert文の構造

assert文は↓のような構造になっています。

assert 式  
assert 式, エラー時のメッセージ  

「式」には検証のための式を渡します。
そしてカンマ区切りで「エラー時のメッセージ」を書きます。
このメッセージは例外AssertionErrorが発生したときにAssertionError()に渡される文字列です。
このメッセージは省略することが出来ます。

「式」の結果がTrueだった場合、assert文は何も発生させません。
「式」の結果がFalseだった場合は、assert文は例外AssertionErrorを発生させます。

assert文は式なので、カッコ(())で引数を囲う必要がありません。

assert文のデバッグモード

assert文は実行時にグローバル変数__debug__を参照します。
この変数がTrueだった場合は処理を実行し、Falseだった場合は処理を実行しません。
assert文の構造が↓であるとき、

assert expression, message  

これはたとえば↓のようなコードと同じです。

if __debug__:  
    if not expression: raise AssertionError(message)  

↑のようにassert文は__debug__Falseの時は何もしません。
プログラムの実行時、__debug__の値はデフォルトではTrueです。
この__debug__の値をFalseにしたい場合はPythonのオプション-Oを指定します。
たとえばprog.pyというプログラム内の__debug__Falseにしたい場合は↓のようにします。

$ python -O prog.py  

__debug__の値をプログラム内で変更するのは不正な操作です。
この値はインタプリタが起動するときに初期化されます。

オプション-Oを指定しているとき、このプログラムは本番モードと見ることが出来るでしょう。
ただあまり「本番モード」と表現している人はいないようです。

assert文の使用例

たとえば↓のように使います。

a = 0  
assert a == 1, 'aが1じゃありません'  

↑のコードを実行すると、↓のような結果になります。

Traceback (most recent call last):  
  File "./prog.py", line 2, in <module>  
    assert a == 1, 'aが1じゃありません'  
AssertionError: aが1じゃありません  

AssertionErrorが発生しています。
変数aの値は0で初期化されていますので、a == 1という式の結果はFalseになります。
よってassert文はAssertionErrorを発生させます。

↓のようにエラー時のメッセージを単純に省略することも出来ます。

a = 0  
assert a == 1  

↑のコードを実行すると、↓のような結果になります。

Traceback (most recent call last):  
  File "./prog.py", line 2, in <module>  
    assert a == 1  
AssertionError  

assert文の使いどころ

assert文はどこで使うべきでしょうか?
基本的にassert文はプログラムの開発を助けるためにあります。
しかしassert文はエラー処理のロジックを書くための文ではありません。

たとえば↓のようにassert文で関数の引数をチェックするとします。

def f(a, b):  
    assert type(a) == int, 'invalid a'  
    assert type(b) == str, 'invalid b'  
    return a + int(b)  

↑の関数fはデバッグモード時に、引数aint型の整数かどうかチェックし、引数bstr型の文字列かどうかチェックします。
↑のようにassert文で関数の引数をチェックすることで、引数のバリデーションとエラー処理が書けているように見えます。
しかし、肝心なのはassert文が本番モード時、つまり__debug__Falseのときに無効になるという点です。
つまり、↑のコードは、本番モードでは↓のコードと同じになります。

def f(a, b):  
    return a + int(b)  

引数のエラーチェックが無くなってしまいました。
このことからわかるように、assert文で本番モード時に必要なエラーのロジックを書くべきではありません。
↑のコードをちゃんと書き直すと↓のようになるでしょう。

def f(a, b):  
    if not isinstance(a, int):  
        raise TypeError('invalid a')  
    if not isinstance(b, str):  
        raise TypeError('invalid b')  
    return a + int(b)  

↑のコードは本番モード時でもちゃんとエラーチェックが機能します。
より詳細には引数bintにキャストできるかどうかもチェックする必要がありますが、ここでは省略しています。

assert文が必要になるとき

繰り返しになりますが、assert文の使いどころは↓です。

  • プログラムが正常に動けば通る処理だが、念のためチェックしておきたい時

この↑の定義はプログラマーによって変わります。
assert文を使う文脈は非常に独特です。

たとえば私はリストに値を追加し、それを取り出す処理を書くときにassertを使うことがあります。
それは↓のようなコードです。

lis = []  
lis.append(1)  
lis.append(2)  

last = lis.pop()  
assert last is not None  

last = lis.pop()  
assert last is not None  

↑のコードでは、リストへのappend()の処理の一連が正常に書かれていれば、リスト内にNoneが入り込むことはありません。
しかし、何かの手違いでリスト内にNoneが紛れ込む可能性も否定できません。
これはプログラマーの手違いによるミスです。
この手違いによるミスはプログラマーがちゃんと仕事をすれば発生することはありません。つまり、これのチェックはプログラムのロジックに組み込む必要が無いということになります。
そういう時に↑のようにassert文を使って、リストから取り出した値がNoneじゃないかどうかチェックします。
仮にNoneが入っていたら、assert文がエラーを発生させます。
そしてこれらのassert文は、プログラムの本番モード時には取り除かれるわけです。

assert文がエラーを発生させれば、プログラマーはリスト内にNoneが入ってることをいち早く知ることが出来ます。
そしてそれはデバッグの助けになります。

おわりに

今回はPythonのassert文について解説しました。
assert文は使い慣れると開発を効率的にすることができます。