Python 開発モード

Added in version 3.7.

Python 開発モードは、コストが高すぎてデフォルトでは有効にできない追加の実行時チェックを行います。 コードが正しければ、デフォルトより多くのメッセージが表示されることはないはずです。 問題を検出したときだけ新たな警告を出します。

-X dev コマンドラインオプションを使うか、PYTHONDEVMODE 環境変数を 1 に設定すると有効にできます。

Python のデバッグビルド も参照してください。

Python 開発モードの影響

Python 開発モードを有効化することは次のコマンドに似ていますが、以下で説明される追加の影響があります:

PYTHONMALLOC=debug PYTHONASYNCIODEBUG=1 python -W default -X faulthandler

Python 開発モードの影響:

  • default の 警告フィルタ を追加します。次の警告が表示されます:

    通常、上記の警告は デフォルトの 警告フィルタ によりフィルタリングされます。

    -W default コマンドラインオプションを使った場合と同じ動作になります。

    警告をエラーとして扱うには、-W error コマンドラインオプションを使うか、PYTHONWARNINGS 環境変数を error に設定してください。

  • メモリアロケーターにデバッグフックを設定し、次の問題をチェックします:

    • バッファーアンダーフロー

    • バッファーオーバーフロー

    • メモリアロケーター API の規則違反

    • GIL の安全でない使用

    C 関数 PyMem_SetupDebugHooks() を参照してください。

    PYTHONMALLOC 環境変数を debug に設定した場合と同じ動作になります。

    メモリアロケーターにデバッグフックを設定せずに Python 開発モードを有効にするには、PYTHONMALLOC 環境変数を default に設定してください。

  • Python 起動時に faulthandler.enable() を呼び、SIGSEGV、SIGFPE、SIGABRT、SIGBUS、SIGILL シグナルのハンドラーを設定して、クラッシュ時に Python のトレースバックを出力します。

    -X faulthandler コマンドラインオプションを使うか、PYTHONFAULTHANDLER 環境変数を 1 に設定した場合と同じ動作になります。

  • asyncio のデバッグモード を有効にします。 例えば asyncio は await されなかったコルーチンを検出してログに記録します。

    PYTHONASYNCIODEBUG 環境変数を 1 に設定した場合と同じ動作になります。

  • 文字列のエンコードとデコードで encoding と errors 引数をチェックします。 対象の例として open()、str.encode()、bytes.decode() があります。

    デフォルトでは性能を優先するため、errors 引数は最初のエンコードまたはデコードのエラー時にだけチェックし、空文字列では encoding 引数を無視する場合があります。

  • io.IOBase のデストラクタが close() の例外をログに記録します。

  • sys.flags の dev_mode 属性を True に設定します。

Python 開発モードでは、性能とメモリのオーバーヘッドが大きすぎるため、tracemalloc モジュールをデフォルトでは有効にしません。 tracemalloc モジュールを有効にすると、一部のエラーの発生元に関する追加情報が得られます。 例えば ResourceWarning はリソース割り当て時のトレースバックを記録し、バッファーオーバーフローのエラーはメモリブロック割り当て時のトレースバックを記録します。

Python 開発モードを有効にしても、-O コマンドラインオプションによる assert 文の削除や、__debug__ の False への設定は妨げられません。

Python 開発モードは Python 起動時にのみ有効にできます。 その値は sys.flags.dev_mode から取得できます。

バージョン 3.8 で変更: io.IOBase のデストラクタが close() の例外をログに記録するようになりました。

バージョン 3.9 で変更: 文字列のエンコードとデコードで encoding と errors 引数をチェックするようになりました。

ResourceWarning の例

コマンドラインで指定したテキストファイルの行数を数えるスクリプトの例です。

import sys

def main():
    fp = open(sys.argv[1])
    nlines = len(fp.readlines())
    print(nlines)
    # ファイルは暗黙的に閉じられる

if __name__ == "__main__":
    main()

このスクリプトはファイルを明示的に閉じていません。 デフォルトでは Python は警告を出しません。 269 行の README.txt を使った例です:

$ python script.py README.txt
269

Python 開発モードを有効にすると ResourceWarning 警告が表示されます:

$ python -X dev script.py README.txt
269
script.py:10: ResourceWarning: unclosed file <_io.TextIOWrapper name='README.rst' mode='r' encoding='UTF-8'>
  main()
ResourceWarning: Enable tracemalloc to get the object allocation traceback

さらに tracemalloc を有効にすると、ファイルを開いた行が表示されます:

$ python -X dev -X tracemalloc=5 script.py README.rst
269
script.py:10: ResourceWarning: unclosed file <_io.TextIOWrapper name='README.rst' mode='r' encoding='UTF-8'>
  main()
Object allocated at (most recent call last):
  File "script.py", lineno 10
    main()
  File "script.py", lineno 4
    fp = open(sys.argv[1])

ファイルを明示的に閉じれば修正できます。 コンテキストマネージャを使った例です。

def main():
    # ブロックを終了する際に明示的にファイルを閉じる
    with open(sys.argv[1]) as fp:
        nlines = len(fp.readlines())
    print(nlines)

リソースを明示的に閉じないと、予想よりずっと長い間開いたままになり、Python 終了時に重大な問題を起こすことがあります。 CPython でも問題ですが、PyPy ではさらに深刻です。 リソースを明示的に閉じることで、アプリケーションの動作がより確定的になり、信頼性も高まります。

Bad file descriptor エラーの例

自分自身の最初の行を表示するスクリプトです。

import os

def main():
    fp = open(__file__)
    firstline = fp.readline()
    print(firstline.rstrip())
    os.close(fp.fileno())
    # ファイルは暗黙的に閉じられる

main()

デフォルトでは Python は警告を出しません:

$ python script.py
import os

Python 開発モードでは ResourceWarning が表示され、ファイルオブジェクトのファイナライズ時に "Bad file descriptor" エラーがログに記録されます:

$ python -X dev script.py
import os
script.py:10: ResourceWarning: unclosed file <_io.TextIOWrapper name='script.py' mode='r' encoding='UTF-8'>
  main()
ResourceWarning: Enable tracemalloc to get the object allocation traceback
Exception ignored in: <_io.TextIOWrapper name='script.py' mode='r' encoding='UTF-8'>
Traceback (most recent call last):
  File "script.py", line 10, in <module>
    main()
OSError: [Errno 9] Bad file descriptor

os.close(fp.fileno()) はファイル記述子を閉じます。 ファイルオブジェクトのファイナライザーがそのファイル記述子を再度閉じようとすると、Bad file descriptor エラーで失敗します。 ファイル記述子は 1 回だけ閉じなければなりません。 2 回閉じると、最悪の場合クラッシュすることがあります (例として bpo-18748 を参照してください)。

os.close(fp.fileno()) の行を削除するか、closefd=False を指定してファイルを開けば修正できます。