pythonでwarningsパッケージを使った警告の表示のメモ

警告を表示したいことがある。例えば、次のversionで廃止予定の関数を使った時の警告など。 こういうときには warningsパッケージを使う。

first step

00warn.py

import warnings


def foo():
    warnings.warn("hmm")
    return "hai"


if __name__ == "__main__":
    print(foo())
    print(foo())

warnings.warn で警告メッセージを表示する。defaultではUserWarningの警告レベルで警告メッセージが出る。同じメッセージは2度表示されない。

$ python 00warn.py
00warn.py:5: UserWarning: hmm
  warnings.warn("hmm")
hai

開発者向けのDeprecationWarning

開発者向けにDeprecationWarningの警告レベルで警告メッセージを表示したい場合がある。これは通常のpythonの実行では表示されない。

01warn.py

import warnings


def foo():
    warnings.warn("hmm")
    warnings.warn("hmm. foo is deprecated", DeprecationWarning)
    return "hai"


if __name__ == "__main__":
    print(foo())
    print(foo())

通常のpythonの実行では表示されない。-W default などを付けて実行すると表示されるようになる

$ python 02warn.py
02warn.py:5: UserWarning: hmm
  warnings.warn("hmm")
hai
hai
$ python -W default 02warn.py
02warn.py:5: UserWarning: hmm
  warnings.warn("hmm")
02warn.py:6: DeprecationWarning: hmm. foo is deprecated
  warnings.warn("hmm. foo is deprecated", DeprecationWarning)
hai
hai

すごくまじめにAPIの機能を廃止する時の作業

APIの機能を廃止する時の作業はまじめにやるなら以下の様な形になる。

  1. PendingDeprecationWarning (将来消される予定のAPI)
  2. DeprecationWarning (既に廃止されているAPI。ただし移行期間用にまだコードは消されていない)
  3. APIを消す

DeprecationWarningは眼に見えないという点がちょっとつらいかもしれない?

一時的に警告をfilterしたい場合

capture_warningsとsimple_filterを組み合せると良い。引数に渡す値は以下くらいの雑な認識良さそう。

  • always 全部表示
  • ignore 全部無視

03warn.py

import warnings


def foo():
    warnings.warn("hmm")
    warnings.warn("hmm. foo is deprecated", DeprecationWarning)
    warnings.warn("hmm. foo is deprecated on next version", PendingDeprecationWarning)
    return "hai"


if __name__ == "__main__":
    print("1.")
    print("----------------------------------------")
    with warnings.catch_warnings():
        warnings.simplefilter("ignore")
        print(foo())

    print("2.")
    print("----------------------------------------")
    with warnings.catch_warnings():
        warnings.simplefilter("always")
        print(foo())

1回目のfoo呼び出しでは全ての警告メッセージが無視される。 2回目のfoo呼び出しでは全ての警告メッセージを表示される。

実行結果。

$ python 03warn.py
1.
----------------------------------------
hai
2.
----------------------------------------
03warn.py:5: UserWarning: hmm
  warnings.warn("hmm")
03warn.py:6: DeprecationWarning: hmm. foo is deprecated
  warnings.warn("hmm. foo is deprecated", DeprecationWarning)
03warn.py:7: PendingDeprecationWarning: hmm. foo is deprecated on next version
  warnings.warn("hmm. foo is deprecated on next version", PendingDeprecationWarning)
hai

警告メッセージを表示するdecoratorを作る場合

警告メッセージを表示するdecoratorを作る場合はwarnings.warnのstacklevelを調整する。

05withdecorator.py

import warnings


def useless(msg, cls=UserWarning):
    def _useless(fn):
        def decorated(*args, **kwargs):
            warnings.warn(msg, stacklevel=2)
            return fn(*args, **kwargs)

        return decorated

    return _useless


@useless("foo is useless")
def foo():
    return "foo"


if __name__ == "__main__":
    print(foo())

呼び出した位置をしっかり警告してくれる。

$ python 05withdecorator.py
05withdecorator.py:21: UserWarning: foo is useless
  print(foo())
foo

警告のテストの書き方

警告のテストについては、ドキュメントにも例が載っている

pyramidというweb application frameworkのテストを例に警告の表示のテストの書き方のメモ。

tests/test_config/test_util.py

class TestDeprecatedPredicates(unittest.TestCase):
    def test_it(self):
        import warnings
        with warnings.catch_warnings(record=True) as w:
            warnings.filterwarnings('always')
            from pyramid.config.predicates import XHRPredicate
            self.assertEqual(len(w), 1)

capture_warningsで記録しておく。警告が記録されたら増えるのでlenで長さを確認する。

see also

この記事は分かりやすかった