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
はデバッグモード時に、引数a
がint
型の整数かどうかチェックし、引数b
がstr
型の文字列かどうかチェックします。
↑のように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)
↑のコードは本番モード時でもちゃんとエラーチェックが機能します。
より詳細には引数b
がint
にキャストできるかどうかもチェックする必要がありますが、ここでは省略しています。
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
文は使い慣れると開発を効率的にすることができます。