Python WebAcademy Blog

Pythonのwarningsとは?DeprecationWarningの正体と、警告を出す・消す・エラーにする方法を初心者向けに解説

|

プログラムは最後まで動いたのに、画面にDeprecationWarningやUserWarningと表示されて不安になったことはありませんか。警告はエラーとは違い、処理を止めずに注意だけを伝える仕組みで、Pythonでは標準ライブラリのwarningsで扱います。警告の読み方、自分で警告を出すwarnings.warnとstacklevel、表示されたりされなかったりする理由、-Wオプションでの抑制とエラー化、3.13で入った@deprecatedまで、手元で動かした結果とともに解説します。

プログラムは最後まで動いたのに、画面に見慣れない英語のメッセージが出ていた。そんな経験はありませんか。よく見るとDeprecationWarningやUserWarningと書いてあって、エラーなのかどうかもわからない。

赤い文字で何か出ていると、それだけで少し不安になりますよね。けれども、慌てて検索する前に知っておきたいことがあります。

これはエラーではなく、警告です。Pythonには、処理を止めずに注意だけを伝えるための仕組みが用意されています。

今回は、その仕組みを担う標準ライブラリのwarningsについて、警告の読み方から、自分で警告を出す方法、消し方、エラーに変える方法まで順番に見ていきます。

警告とエラーは何が違うのか

まずは、エラーと警告の違いをはっきりさせておきましょう。ここがわかると、画面に何か出たときの落ち着き方がまったく変わります。

エラー、つまり例外が起きると、Pythonはその場で処理を止めます。try文で受け止めない限り、プログラムはそこで終わりです。

一方で警告は、メッセージを表示するだけで、処理はそのまま続きます。いまは動くけれど、このままだと将来困るかもしれない、という種類の知らせです。

違いを表にまとめると、次のようになります。

項目 エラー(例外) 警告
処理 その場で止まる そのまま続く
主な意味 いま動かない いまは動くが注意が必要
出し方 raise warnings.warn()
受け止め方 try / except フィルターで表示・非表示・エラー化を選ぶ
同じ場所で何度も起きたとき 毎回起きる 初期設定では最初の1回だけ表示

最後の行は、意外と知られていないポイントです。ループの中で同じ警告が出ても、初期設定では1回しか表示されません。

例外の考え方そのものに自信がない方は、先にこちらを読んでおくと理解しやすくなります。【関連記事】やってはいけない例外処理

警告メッセージの読み方

では、実際の警告メッセージを見てみましょう。読み方さえわかれば、どこを直せばいいかはすぐに見えてきます。

次のような短いスクリプトを、w.pyという名前で保存して実行してみます。

import warnings

def old_func():
    warnings.warn(
        "old_funcは非推奨です。new_funcを使ってください",
        DeprecationWarning,
        stacklevel=2,
    )
    return 1

def load(path):
    warnings.warn("設定ファイルが見つからないので既定値を使います", UserWarning)
    return {}

old_func()
load("config.toml")
print("done")

手元のPython 3.11で実行すると、次のように表示されました。表示されるパスは、実行した場所によって変わります。

w.py:15: DeprecationWarning: old_funcは非推奨です。new_funcを使ってください
  old_func()
w.py:12: UserWarning: 設定ファイルが見つからないので既定値を使います
  warnings.warn("設定ファイルが見つからないので既定値を使います", UserWarning)
done

1行目は、ファイル名、行番号、警告の種類、メッセージの順に並んでいます。2行目には、その行のコードがそのまま表示されます。

そして最後にdoneが出ているので、処理は最後まで進んでいます。警告が出ても止まらない、というのがよくわかりますね。

警告にも種類がある

警告には、目的ごとにいくつかの種類があります。カテゴリと呼ばれ、どれもWarningというクラスを親に持っています。

よく見かけるものを表にまとめました。

カテゴリ 主な意味
UserWarning 種類を指定しなかったときの既定。一般的な注意
DeprecationWarning 非推奨。将来のバージョンで削除や変更の予定がある
PendingDeprecationWarning まだ先だが、いずれ非推奨になる予定
FutureWarning 将来、動きが変わる予定。利用者向けに表示される
RuntimeWarning 実行時の怪しい動き
SyntaxWarning 文法としては通るが怪しい書き方
ResourceWarning ファイルなどの閉じ忘れ

初心者がいちばんよく出会うのは、DeprecationWarningとFutureWarningでしょう。pandasなどのライブラリを使っていると、FutureWarningはかなりの頻度で目にします。

自分で警告を出してみよう

警告は、見るだけでなく自分で出すこともできます。使うのはwarnings.warn()です。

第1引数にメッセージ、第2引数にカテゴリを渡します。カテゴリを省略すると、UserWarningになります。

どんなときに使うと便利なのでしょうか。たとえば、処理は続けられるけれど利用者に知らせておきたいことがある場面です。

先ほどのload関数のように、設定ファイルが無いので既定値で続ける、というケースが典型的です。止めるほどではないけれど、黙っているのも不親切だ、というときに警告がちょうどいい道具になります。

stacklevelで、呼び出した側の行を指す

先ほどのold_funcでは、stacklevel=2という引数を付けていました。これは、警告を出す場所として、どの行を表示するかを決める引数です。

既定の1のままだと、warnings.warn()を書いた行、つまり関数の中の行が表示されます。2にすると、その関数を呼び出した側の行が表示されます。

非推奨の警告を見た人が直すべきなのは、関数の中身ではなく、呼び出している自分のコードのほうです。だから、自作の関数で非推奨を伝えるならstacklevel=2を付けるのがおすすめです。

実際、先ほどの実行結果でも、DeprecationWarningのほうはold_func()を呼び出した15行目を指していました。stacklevelを付けていないUserWarningは、関数の中の12行目を指しています。

DeprecationWarningが出たり出なかったりする理由

ここで、少しふしぎな動きを紹介します。同じDeprecationWarningなのに、表示されるときとされないときがあるのです。

試しに、先ほどのold_funcを別のファイルmylib.pyに移し、そこにある別の関数から呼び出すようにしてみます。

# mylib.py
import warnings

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

def helper():
    old_func()

そして、メインのスクリプトからmylib.helper()を呼びます。すると、警告は何も表示されず、最後のprintの結果だけが出ました。

これは故障ではなく、Pythonの初期設定によるものです。Python 3.7からは、DeprecationWarningは__main__、つまり直接実行したスクリプトのコードが原因のときだけ表示されるようになりました。

ライブラリの内部で起きたDeprecationWarningは、初期設定では表示されずに捨てられます。利用者が直せない警告で画面が埋まらないようにするための配慮です。

Python 3.13で初期設定の中身を確かめると、次のような順番でフィルターが並んでいました。

順番 動作 対象
1 default(場所ごとに1回表示) __main__で起きたDeprecationWarning
2 ignore(無視) それ以外のDeprecationWarning
3 ignore(無視) PendingDeprecationWarning
4 ignore(無視) ImportWarning
5 ignore(無視) ResourceWarning

上から順に見ていき、最初に当てはまったものが使われます。UserWarningやFutureWarningはどれにも当てはまらないので、場所ごとに1回表示されます。

隠れた警告を見るには

では、ライブラリの中に隠れている警告を見たいときは、どうすればいいのでしょうか。いちばん手軽なのは、開発モードで実行することです。

python -X dev main.py

こうすると、先ほどは表示されなかったmylib.pyの警告が表示されました。python -W default::DeprecationWarning main.pyと書いても、同じように見られます。

私は10年ほどエンジニアとして開発に関わってきましたが、Pythonのバージョンを上げたとたんに動かなくなったコードを調べると、実はずっと前から非推奨の警告が隠れていた、ということが何度もありました。バージョンアップの前に一度この方法で警告を洗い出しておくと、当日の作業がぐっと楽になります。

バージョンアップの進め方については、こちらでも解説しています。【関連記事】Pythonのバージョンを変更する方法

警告を消す方法と、エラーに変える方法

警告の表示は、フィルターで変えられます。フィルターには、次のような動作を指定できます。

動作 意味
default 場所ごとに最初の1回だけ表示する
always 毎回表示する
once 場所に関係なく、同じメッセージは1回だけ表示する
module モジュールごとに1回だけ表示する
ignore 表示しない
error 例外に変えて、処理を止める

設定する方法は大きく2つあります。コマンドラインで指定するか、コードの中で指定するかです。

コマンドラインで指定する

実行するときに-Wオプションを付けると、そのときだけフィルターを変えられます。たとえば-W errorを付けると、すべての警告が例外になります。

python -W error w.py

実際に試すと、最初のDeprecationWarningが例外として発生し、トレースバックを表示してそこで止まりました。doneは表示されません。

警告をエラーに変えると、どこから出たのかをトレースバックでたどれるようになります。トレースバックの読み方は、こちらで詳しく紹介しています。【関連記事】Pythonのトレースバックの読み方

同じ指定は、環境変数PYTHONWARNINGSでもできます。毎回オプションを書くのが面倒なときに便利です。

コードの中で指定する

コードの中では、warnings.filterwarnings()やwarnings.simplefilter()を使います。ただし、プログラム全体の警告を一律に消すのはおすすめしません。

本当に困るのは、消したことを忘れて、大事な警告まで見えなくなることです。そこで使いたいのが、warnings.catch_warnings()です。

with文の中だけフィルターを変え、抜けると元の設定に戻してくれます。

import warnings

with warnings.catch_warnings():
    warnings.simplefilter("ignore", FutureWarning)
    result = some_library_call()  # この中のFutureWarningだけを消す

# withを抜けたら、元の設定に戻っている

消す範囲は、できるだけ狭くしておきましょう。どの警告をなぜ消したのか、コメントを1行残しておくと、あとで読む人が助かります。

以前、ログが警告で埋まって読みにくいという理由で、プログラムの先頭で全部の警告を無視する設定が入っていた現場がありました。その結果、ライブラリの更新で動きが変わることを知らせるFutureWarningも見逃し、集計結果がこっそり変わっていたのです。

警告をテストで確かめる

自分で警告を出すようにしたら、それが本当に出るかもテストで確かめておきたくなります。catch_warnings()にrecord=Trueを渡すと、出た警告をリストとして受け取れます。

import warnings

with warnings.catch_warnings(record=True) as caught:
    warnings.simplefilter("always")
    warnings.warn("a", UserWarning)
    warnings.warn("b", DeprecationWarning)

print(len(caught), [w.category.__name__ for w in caught])

実行すると、2 ['UserWarning', 'DeprecationWarning']と表示されました。simplefilter("always")を入れているので、初期設定では捨てられるDeprecationWarningも記録されています。

pytestを使っているなら、pytest.warns()を使うともっと簡単に書けます。警告の種類とメッセージを指定して、出なければテストを失敗にできます。【関連記事】pytestの使い方

Python 3.13から使える@deprecated

最後に、比較的新しい機能を紹介します。Python 3.13から、warningsモジュールに@deprecatedというデコレーターが入りました。

関数やクラスの上に付けるだけで、呼び出されたときにDeprecationWarningを出してくれます。手元で確かめたところ、Python 3.12には無く、3.13には存在していました。

from warnings import deprecated

@deprecated("old_addは非推奨です。addを使ってください")
def old_add(a, b):
    return a + b

print(old_add(1, 2))

Python 3.13で実行すると、old_add(1, 2)を呼んだ行を指して、指定したメッセージのDeprecationWarningが表示されました。そのあと、計算結果の3も表示されています。

このデコレーターの良いところは、実行時だけでなく、mypyなどの型チェッカーにも非推奨であることを伝えられる点です。エディターによっては、呼び出した場所に取り消し線を引いて知らせてくれます。

3.12以前でも同じことをしたい場合は、外部パッケージのtyping_extensionsに同じ名前のデコレーターがあります。プロジェクトで使っているPythonのバージョンに合わせて選びましょう。

まとめ

警告は、処理を止めずに注意だけを伝える仕組みです。エラーと違って見逃しやすいぶん、出たときに読めるようになっておくと、トラブルを早めに防げます。

大事なポイントは、次の3つです。自作の非推奨はstacklevel=2で呼び出し側を指すこと、ライブラリのDeprecationWarningは初期設定では隠れていること、そして警告を消すならcatch_warningsで範囲を狭くすること。

画面に警告が出ても、もう慌てる必要はありません。まずはファイル名と行番号、そしてカテゴリを読んでみてください。

ここまでお読みいただきありがとうございました。

参考情報

次のアクション

記事で学んだ内容を実際に動かしてみよう

Python WebAcademyでは、ブラウザ上でコードを書きながら基礎から実践まで体系的に学べます。

Python WebAcademyの学習画面

あわせて読む

関連記事

ブログ一覧へ

Python学習ロードマップ

まずはこの3講座から

記事で気になったテーマを、順番に手を動かしながら学べます。

ロードマップを見る