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()で十分 - スレッドセーフではない



