Python catch_warningsとは?警告を一時的に制御・記録する方法

warnings.catch_warnings()は、警告の設定を一時的に変更するwith文用の仕組みです。

ブロックを抜けると設定が自動で元に戻ります。


設定が元に戻る

import warnings

def risky():
    warnings.warn("テスト用の警告")

warnings.simplefilter("ignore")

with warnings.catch_warnings():
    warnings.simplefilter("always")
    risky()

print("外では ignore のまま:", warnings.filters[0])

実行結果:

w5.py:4: UserWarning: テスト用の警告
  warnings.warn("テスト用の警告")
外では ignore のまま: ('ignore', None, <class 'Warning'>, None, 0)

withの中だけalwaysになり、抜けるとignoreに戻っています。

simplefilter()やfilterwarnings()を直接呼ぶとプログラム全体に影響が残ります。catch_warnings()ならその心配がありません。


警告を記録する

import warnings

with warnings.catch_warnings(record=True) as w:
    warnings.simplefilter("always")
    risky()
    risky()

    print("記録件数:", len(w))
    print("1件目:", w[0].category.__name__, w[0].message)
    print("発生場所:", w[0].filename, w[0].lineno)

実行結果:

記録件数: 2
1件目: UserWarning テスト用の警告
発生場所: w5.py 4

record=Trueを付けると、警告が表示されずリストに溜まります。

各要素から取れる情報です。

属性 内容
category 警告のクラス
message 警告オブジェクト(文字列ではない)
filename 発生したファイル
lineno 行番号

messageは文字列ではなく警告オブジェクトです。文字列にするにはstr()を通してください。

    print(str(w[0].message))

simplefilterを忘れない

with warnings.catch_warnings(record=True) as w:
    risky()
    risky()
    print("記録件数:", len(w))

実行結果:

記録件数: 1

2回呼んだのに1件しか記録されていません。

既定のフィルタ(default)が効いていて、同じ場所からの2回目が抑制されたためです。

record=Trueを使うときは、必ずsimplefilter("always")をセットで書いてください。

with warnings.catch_warnings(record=True) as w:
    warnings.simplefilter("always")      # これが必須
    ...

これは非常に見落としやすい落とし穴です。


テストで警告を検証する

import unittest
import warnings

def old_api():
    warnings.warn("old_api は非推奨です", DeprecationWarning, stacklevel=2)
    return 1

class T(unittest.TestCase):
    def test_deprecated(self):
        with warnings.catch_warnings(record=True) as w:
            warnings.simplefilter("always")
            old_api()

            self.assertEqual(len(w), 1)
            self.assertTrue(issubclass(w[0].category, DeprecationWarning))
            self.assertIn("非推奨", str(w[0].message))

「正しく警告が出ているか」をテストできます。

unittestには専用の書き方もあります。

        with self.assertWarns(DeprecationWarning):
            old_api()

単に「出たかどうか」を見るならassertWarns()のほうが簡潔です。

件数や順序まで細かく検証したい場合にcatch_warnings(record=True)を使います。

「Python assertRaises()とは?例外が出ることをテストする方法」も参照してください。


外部ライブラリの警告を黙らせる

import warnings

with warnings.catch_warnings():
    warnings.simplefilter("ignore")
    import 古いライブラリ
    古いライブラリ.処理()

その部分だけ警告を消せます。

プログラム全体でignoreにすると、自分のコードの警告まで見えなくなります。範囲を限定するのが安全です。

とはいえ、まずは警告の内容を読んで対処できないか検討してください。黙らせるのは最後の手段です。


スレッドセーフではない

公式ドキュメントに明記されている制限です。

catch_warnings()はフィルタの設定をグローバルに書き換えて、あとで戻す仕組みです。

そのため、複数のスレッドが同時に使うと互いの設定を壊し合います。

  • 他のスレッドの警告まで消してしまう
  • 戻すタイミングが重なって設定が壊れる

マルチスレッドのコードでは使わないでください。テストや初期化処理など、単一スレッドの場面に限定するのが原則です。

「Python threadingとは?スレッド処理の基本を解説」も参照してください。


catch_warningsのまとめ

  • withを抜けるとフィルタ設定が元に戻る
  • record=Trueで警告をリストに記録できる
  • simplefilter("always")とセットで書く——忘れると取りこぼす
  • messageは文字列ではなく警告オブジェクト
  • 単純な検証はassertWarns()で十分
  • スレッドセーフではない

あわせて読みたい

プログラミング学習に興味がある方は、まずは無料体験会へ

無料体験会・お申し込みはこちら