29.5. warnings --- 警告の制御

ソースコード: Lib/warnings.py


警告メッセージは一般に、ユーザに警告しておいた方がよいような状況下にプログラムが置かれているが、その状況は (通常は) 例外を送出したりそのプログラムを終了させるほどの正当な理由がないといった状況で発されます。例えば、プログラムが古いモジュールを使っている場合には警告を発したくなるかもしれません。

Python プログラマは、このモジュールの warn() 関数を使って警告を発することができます。(C 言語のプログラマは PyErr_WarnEx() を使います; 詳細は 例外処理 を参照してください)。

警告メッセージは通常 sys.stderr に出力されますが、その処理方法は柔軟に変更することができます。すべての警告を無視することも、警告を例外に変更することもできます。警告の処理方法は警告カテゴリ (以下参照)、警告メッセージテキスト、そして警告を発したソースコード上の場所に基づいて変更することができます。ソースコード上の同じ場所に対して特定の警告が繰り返された場合、通常は抑制されます。

警告制御には 2 つの段階 (stage) があります: 第一に、警告が発されるたびに、メッセージを出力すべきかどうかの決定が行われます; 次に、メッセージを出力するなら、メッセージはユーザによって設定が可能なフックを使って書式化され印字されます。

警告メッセージを出力するかどうかの決定は、警告フィルタによって制御されます。警告フィルタは一致規則 (matching rule)と動作からなるシーケンスです。 filterwarnings() を呼び出して一致規則をフィルタに追加することができ、 resetwarnings() を呼び出してフィルタを標準設定の状態にリセットすることができます。

警告メッセージの印字は showwarning() を呼び出して行うことができ、この関数は上書きすることができます; この関数の標準の実装では、 formatwarning() を呼び出して警告メッセージを書式化しますが、この関数についても自作の実装を使うことができます。

参考

logging.captureWarnings() を使うことで、標準ロギング基盤ですべての警告を扱うことができます。

29.5.1. 警告カテゴリ

警告カテゴリを表現する組み込み例外は数多くあります。このカテゴリ化は警告をグループごとフィルタする上で便利です。現在以下の警告カテゴリクラスが定義されています:

Class 説明
Warning すべての警告カテゴリクラスの基底クラスです。 Exception のサブクラスです。
UserWarning warn() の標準のカテゴリです。
DeprecationWarning その機能が廃止されていることを示す警告カテゴリの基底クラスです (デフォルトでは無視されます)。
SyntaxWarning その文法機能があいまいであることを示す警告カテゴリの基底クラスです。
RuntimeWarning そのランタイム機能があいまいであることを示す警告カテゴリの基底クラスです。
FutureWarning その構文の意味付けが将来変更される予定であることを示す警告カテゴリの基底クラスです。
PendingDeprecationWarning 将来その機能が廃止されることを示す警告カテゴリの基底クラスです(デフォルトでは無視されます)。
ImportWarning モジュールのインポート処理中に引き起こされる警告カテゴリの基底クラスです(デフォルトでは無視されます)。
UnicodeWarning Unicode に関係した警告カテゴリの基底クラスです。
BytesWarning bytesbytearray に関連した警告カテゴリの基底クラスです。
ResourceWarning リソースの使用に関連した警告カテゴリの基底クラスです。

これらは厳密に言えば組み込み例外ですが、概念的には警告メカニズムに属しているのでここで記述されています。

標準の警告カテゴリをユーザの作成したコード上でサブクラス化することで、さらに別の警告カテゴリを定義することができます。警告カテゴリは常に Warning クラスのサブクラスでなければなりません。

29.5.2. 警告フィルタ

警告フィルタは、ある警告を無視すべきか、表示すべきか、あるいは (例外を送出する) エラーにするべきかを制御します。

概念的には、警告フィルタは複数のフィルタ仕様からなる順番リストを維持しています; 何らかの特定の警告が生じると、一致するものが見つかるまでリスト中の各フィルタとの照合が行われます; 一致したフィルタ仕様がその警告の処理方法を決定します。フィルタの各エントリは (action, message, category, module, lineno) からなるタプルです。ここで:

  • action は以下の文字列のうちの一つです:

    処理方法
    "error" 一致した警告を例外に変えます
    "ignore" 一致した警告を出力しません
    "always" 一致した警告を常に出力します
    "default" 一致した警告のうち、警告の原因になったソースコード上の場所ごとに、最初の警告のみ出力します
    "module" 一致した警告のうち、警告の原因になったモジュールごとに、最初の警告のみ出力します
    "once" 一致した警告のうち、警告の原因になった場所にかかわらず最初の警告のみ出力します
  • message は正規表現を含む文字列で、警告メッセージの先頭はこのパターンに一致しなければなりません。正規表現は常に大小文字の区別をしないようにコンパイルされます。

  • category はクラス (Warning のサブクラス) です。警告クラスはこのクラスのサブクラスに一致しなければなりません。

  • module は正規表現を含む文字列で、モジュール名はこのパターンに一致しなければなりません。正規表現は常に大小文字の区別をしないようにコンパイルされます。

  • lineno は整数で、警告が発生した場所の行番号に一致しなければなりません。0 の場合はすべての行と一致します。

Warning クラスは組み込みの Exception クラスから派生しているので、警告をエラーに変換するには単に category(message)raise します。

警告フィルタは Python インタプリタのコマンドラインに渡される -W オプションで初期化されます。インタプリタは -W オプションに渡されるすべての引数を sys.warnoptions に変換せずに保存します; warnings モジュールは最初に import された際にこれらの引数を解釈します (無効なオプションは sys.stderr にメッセージを出力した上で無視されます)。

29.5.2.1. デフォルトの警告フィルタ

デフォルトで、 Python はいくつかの警告フィルタをインストールします。これはコマンドラインオプション -Wfilterwarnings() 関数でオーバーライドできます。

バージョン 3.2 で変更: PendingDeprecationWarning に加えて、 DeprecationWarning もデフォルトで無視されるようになりました。

29.5.3. 一時的に警告を抑制する

廃止予定の関数など、警告を発生させることが分かっているコードを利用する場合に、そのような警告を表示したくなければ、 catch_warnings コンテキストマネージャーを使って警告を抑制することができます:

import warnings

def fxn():
    warnings.warn("deprecated", DeprecationWarning)

with warnings.catch_warnings():
    warnings.simplefilter("ignore")
    fxn()

このサンプルのコンテキストマネージャーの中では、すべての警告が無視されています。これで、他の廃止予定のコードを含まない(つもりの)部分まで警告を抑止せずに、廃止予定だと分かっているコードだけ警告を表示させないようにすることができます。注意: これが保証できるのはシングルスレッドのアプリケーションだけです。 2つ以上のスレッドが同時に catch_warnings コンテキストマネージャーを使用した場合、動作は未定義です。

29.5.4. 警告のテスト

コードが警告を発生させることをテストするには、 catch_warnings コンテキストマネージャーを利用します。このクラスを使うと、一時的に警告フィルタを操作してテストに利用できます。例えば、次のコードでは、発生したすべての警告を取得してチェックしています:

import warnings

def fxn():
    warnings.warn("deprecated", DeprecationWarning)

with warnings.catch_warnings(record=True) as w:
    # Cause all warnings to always be triggered.
    warnings.simplefilter("always")
    # Trigger a warning.
    fxn()
    # Verify some things
    assert len(w) == 1
    assert issubclass(w[-1].category, DeprecationWarning)
    assert "deprecated" in str(w[-1].message)

always の代わりに error を利用することで、すべての警告で例外を発生させることができます。1つ気をつけないといけないのは、一度 once/default ルールによって発生した警告は、フィルタに何をセットしているかにかかわらず、警告レジストリをクリアしない限りは 2度と発生しません。

コンテキストマネージャーが終了したら、警告フィルタはコンテキストマネージャーに入る前のものに戻されます。これは、テスト中に予期しない方法で警告フィルタが変更され、テスト結果が中途半端になる事を予防します。このモジュールの showwarning() 関数も元の値に戻されます。注意: これが保証できるのはシングルスレッドのアプリケーションだけです。 2つ以上のスレッドが同時に catch_warnings コンテキストマネージャを使用した場合、動作は未定義です。

同じ種類の警告を発生させる複数の操作をテストする場合、各操作が新しい警告を発生させている事を確認するのは大切な事です (例えば、警告を例外として発生させて各操作が例外を発生させることを確認したり、警告リストの長さが各操作で増加していることを確認したり、警告リストを各操作の前に毎回クリアする事ができます)。

29.5.5. コードを新しいバージョンの Python のために更新する

開発者にしか興味の無い警告はデフォルトでは無視されるようになりました。なので、通常、コードをテストするときには無視されている例外を表示するようにするべきです。これは -Wd <-W> コマンドライン引数 (-W default の省略形) をインタプリタに指定することで可能です。これはデフォルトでは無視されているものも含めた全ての警告に対して、デフォルトの動作を有効にします。この動作を変更するためには、 -W への引数を変更します。例えば、 -W error のようにします。何が可能かについての詳細は、 -W フラグを参照してください。

プログラムから -Wd と同じ事をするには、次のようにします:

warnings.simplefilter('default')

このコードは可能な限り早く実行してください。そうすることで、どの警告を発生させるかの登録が、将来の警告がどう扱われるかについて思わぬ影響を与えることを避けることができます。

いくつかの警告がデフォルトで無視されているのは、開発者向けの警告をユーザーに見せることを避けるためです。ユーザーがどのインタプリタを使ってコードを実行するのかを必ずしも制御できるわけではないので、自分のコードのリリースサイクルの間に新しいバージョンの Python がリリースされるかもしれません。新しいインタプリタは、古いインタプリタが出さなかった新しい警告を発生させるかもしれません。例えば、利用しているモジュールに対して DeprecationWarning が発生されることがあります。開発者としては、廃止予定のモジュールを利用していることに対する通知は有益ですが、一般ユーザーにとってはこの情報はノイズでしか無く、何の役にも立ちません。

unittest モジュールは 、テストを実行する際に 'default' フィルタを使用するように更新されました。

29.5.6. 利用可能な関数

warnings.warn(message, category=None, stacklevel=1)

警告を発するか、無視するか、あるいは例外を送出します。 category 引数が与えられた場合、警告カテゴリクラスでなければなりません (上記を参照してください); 標準の値は UserWarning です。 messageWarning インスタンスで代用することもできますが、この場合 category は無視され、 message.__class__ が使われ、メッセージ文は str(message) になります。発された例外が前述した警告フィルタによってエラーに変更された場合、この関数は例外を送出します。引数 stacklevel は Python でラッパー関数を書く際に利用することができます。例えば:

def deprecation(message):
    warnings.warn(message, DeprecationWarning, stacklevel=2)

こうすることで、警告が参照するソースコード部分を、 deprecation() 自身ではなく deprecation() を呼び出した側にできます (というのも、前者の場合は警告メッセージの目的を台無しにしてしまうからです)。

warnings.warn_explicit(message, category, filename, lineno, module=None, registry=None, module_globals=None)

warn() の機能に対する低レベルのインタフェースで、メッセージ、警告カテゴリ、ファイル名および行番号、そしてオプションのモジュール名およびレジストリ情報 (モジュールの __warningregistry__ 辞書) を明示的に渡します。モジュール名は標準で .py が取り去られたファイル名になります; レジストリが渡されなかった場合、警告が抑制されることはありません。 message が文字列のとき、 categoryWarning のサブクラスでなければなりません。また messageWarning のインスタンスであってもよく、この場合 category は無視されます。

module_globals は、もし与えられるならば、警告が発せられるコードが使っているグローバル名前空間でなければなりません (この引数は zipfile やその他の非ファイルシステムのインポート元の中にあるモジュールのソースを表示することをサポートするためのものです)。

warnings.showwarning(message, category, filename, lineno, file=None, line=None)

警告をファイルに書き込みます。標準の実装では、 formatwarning(message, category, filename, lineno, line) を呼び出し、返された文字列を file に書き込みます。 file は標準では sys.stderr です。この関数は warnings.showwarning に任意の呼び出し可能オブジェクトを代入して置き換えることができます。 line は警告メッセージに含めるソースコードの1行です。 line が与えられない場合、 showwarning()filenamelineno から行を取得することを試みます。

warnings.formatwarning(message, category, filename, lineno, line=None)

警告を通常の方法で書式化します。返される文字列内には改行が埋め込まれている可能性があり、かつ文字列は改行で終端されています。 line は警告メッセージに含まれるソースコードの1行です。 line が渡されない場合、 formatwarning()filenamelineno から行の取得を試みます。

warnings.filterwarnings(action, message='', category=Warning, module='', lineno=0, append=False)

警告フィルタ仕様 のリストにエントリを一つ挿入します。標準ではエントリは先頭に挿入されます; append が真ならば、末尾に挿入されます。この関数は引数の型をチェックし、 message および module の正規表現をコンパイルしてから、これらをタプルにして警告フィルタのリストに挿入します。二つのエントリが特定の警告に合致した場合、リストの先頭に近い方のエントリが後方にあるエントリに優先します。引数が省略されると、標準ではすべてにマッチする値に設定されます。

warnings.simplefilter(action, category=Warning, lineno=0, append=False)

単純なエントリを 警告フィルタ仕様 のリストに挿入します。引数の意味は filterwarnings() と同じですが、この関数により挿入されるフィルタはカテゴリと行番号が一致していればすべてのモジュールのすべてのメッセージに合致しますので、正規表現は必要ありません。

warnings.resetwarnings()

警告フィルタをリセットします。これにより、 -W コマンドラインオプションによるもの simplefilter() 呼び出しによるものを含め、 filterwarnings() の呼び出しによる影響はすべて無効化されます。

29.5.7. 利用可能なコンテキストマネージャー

class warnings.catch_warnings(*, record=False, module=None)

警告フィルタと showwarning() 関数をコピーし、終了時に復元するコンテキストマネージャーです。 record 引数が False (デフォルト値)だった場合、コンテキスト開始時には None を返します。もし recordTrue だった場合、リストを返します。このリストにはカスタムの showwarning() 関数(この関数は同時に sys.stdout への出力を抑制します)によってオブジェクトが継続的に追加されます。リストの中の各オブジェクトは、 showwarning() 関数の引数と同じ名前の属性を持っています。

module 引数は、保護したいフィルタを持つモジュールを取ります。 warnings を import して得られるモジュールの代わりに利用されます。この引数は、主に warnings モジュール自体をテストする目的で追加されました。

注釈

catch_warnings マネージャーは、モジュールの showwarning() 関数と内部のフィルタ仕様のリストを置き換え、その後復元することによって動作しています。これは、コンテキストマネージャーがグローバルな状態を変更していることを意味していて、したがってスレッドセーフではありません。