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" 開
啟檔案。
