# Python 開發模式

在 3.7 版被加入.

Python 開發模式引入了額外的 runtime 檢查，預設啟用這些檢查的成本太高。如果程式碼正確，它不應比預設值更詳細；僅當偵測到問題時才會發出新警告。

可以使用 `-X dev` 命令列選項或將 `PYTHONDEVMODE` 環境變數設為 1 來啟用它。

另請參閱 Python 除錯建置。

## Python 開發模式的影響

啟用 Python 開發模式類似以下指令，但具有如下所述的附加效果：

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

Python 開發模式的效果：

- 新增 `default` 警告過濾器。以下警告會被顯示：

  - `DeprecationWarning`

  - `ImportWarning`

  - `PendingDeprecationWarning`

  - `ResourceWarning`

  一般來說，上述警告會被預設的警告過濾器給過濾掉。

  它的行為就像使用 `-W default` 命令列選項一樣。

  使用 `-W error` 命令列選項或將 `PYTHONWARNINGS` 環境變數設為 `error` 會將警告視為錯誤。

- 在記憶體分配器上安裝除錯 hook（掛鉤）以檢查：

  - 緩衝區下溢 (underflow)

  - 緩衝區溢位 (overflow)

  - 記憶體分配器 API 違規

  - GIL 的不安全使用

  請參閱 `PyMem_SetupDebugHooks()` C 函式。

  它的行為就好像是將 `PYTHONMALLOC` 環境變數設定為 `debug` 一樣。

  若要啟用 Python 開發模式而不在記憶體分配器上安裝偵錯 hook，請將 `PYTHONMALLOC` 環境變數設為 `default`。

- 在 Python 啟動時呼叫 `faulthandler.enable()` 來為 `SIGSEGV`、`SIGFPE`、`SIGABRT`、`SIGBUS` 和 `SIGILL` 訊號安裝處理函式以在當機時傾印 (dump) Python 回溯 (traceback)。

  它的行為就像使用 `-X faulthandler` 命令列選項或將 `PYTHONFAULTHANDLER` 環境變數設定為 `1`。

- 啟用 asyncio 除錯模式。例如 `asyncio` 會檢查未被等待的 (not awaited) 協程並記錄 (log) 它們。

  它的行為就像將 `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)
        # The file is closed implicitly

    if __name__ == "__main__":
        main()

該腳本不會明確關閉檔案。預設情況下，Python 不會發出任何警告。使用 README.txt 的範例，該檔案有 269 列：

    $ 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 區塊時明確關閉檔案
        with open(sys.argv[1]) as fp:
            nlines = len(fp.readlines())
        print(nlines)

不明確關閉資源可能會使資源開啟的時間比預期的長得多；它可能會在退出 Python 時導致嚴重問題。在 CPython 中很糟糕，但在 PyPy 中更糟。明確關閉資源使應用程式更具確定性和可靠性。

## 檔案描述器的錯誤範例

顯示自身第一列的腳本：

    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 開發模式在最終化 (finalize) 檔案物件時顯示 `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())` 會關閉檔案描述器。當檔案物件最終化函式 (finalizer) 嘗試再次關閉檔案描述器時，它會失敗並出現 `Bad file descriptor` 錯誤。檔案描述器只能關閉一次。在最壞的情況下，將它關閉兩次可能會導致崩潰 (crash)（相關範例請參閱 bpo-18748）。

修復方法是刪除 `os.close(fp.fileno())` 那列，或使用 `closefd=False` 開啟檔案。