Python 開(kāi)發(fā)模式?

3.7 新版功能.

開(kāi)發(fā)模式下的 Python 加入了額外的運行時(shí)檢查,由于開(kāi)銷(xiāo)太大,并非默認啟用的。如果代碼能夠正確執行,默認的調試級別足矣,不應再提高了;僅當覺(jué)察到問(wèn)題時(shí)再提升警告觸發(fā)的級別。

使用 -X dev 命令行參數或將環(huán)境變量 PYTHONDEVMODE 置為 1 ,可以啟用開(kāi)發(fā)模式。

另請參考 Python debug build 。

Python 開(kāi)發(fā)模式的效果?

啟用 Python 開(kāi)發(fā)模式后的效果,與以下命令類(lèi)似,不過(guò)還有下面的額外效果:

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

Python 開(kāi)發(fā)模式的效果:

  • 加入 default warning filter 。下述警告信息將會(huì )顯示出來(lái):

    通常上述警告是由默認的 warning filters 負責處理的。

    效果類(lèi)似于采用了 -W default 命令行參數。

    使用命令行參數 -W error 或將環(huán)境變量 PYTHONWARNINGS 設為 error,可將警告視為錯誤。

  • 在內存分配程序中安裝調試鉤子,用以查看:

    • 緩沖區下溢

    • 緩沖區上溢

    • 內存分配 API 沖突

    • 不安全的 GIL 調用

    參見(jiàn) C 函數 PyMem_SetupDebugHooks() 。

    效果如同將環(huán)境變量 PYTHONMALLOC 設為 debug。

    若要啟用 Python 開(kāi)發(fā)模式,卻又不要在內存分配程序中安裝調試鉤子,請將 環(huán)境變量 PYTHONMALLOC 設為 default。

  • 在啟動(dòng) Python 時(shí)調用 faulthandler.enable() ,會(huì )安裝 SIGSEGV 、 SIGFPE 、 SIGABRT 、 SIGBUSSIGILL 信號的處理程序,以便在程序崩潰時(shí)將 Python 跟蹤信息轉儲下來(lái)。

    其行為如同使用了 -X faulthandler 命令行選項或將 PYTHONFAULTHANDLER 環(huán)境變量設為 1。

  • 啟用 asyncio debug mode。比如 asyncio 會(huì )檢查沒(méi)有等待的協(xié)程并記錄下來(lái)。

    效果如同將環(huán)境變量 PYTHONASYNCIODEBUG 設為 1。

  • 檢查字符串編碼和解碼函數的 encodingerrors 參數。例如: open() 、 str.encode()bytes.decode()。

    為了獲得最佳性能,默認只會(huì )在第一次編碼/解碼錯誤時(shí)才會(huì )檢查 errors 參數,有時(shí) encoding 參數為空字符串時(shí)還會(huì )被忽略。

  • io.IOBase 的析構函數會(huì )記錄 close() 觸發(fā)的異常。

  • sys.flagsdev_mode 屬性設為 True。

Python 開(kāi)發(fā)模式下,默認不會(huì )啟用 tracemalloc 模塊,因為其性能和內存開(kāi)銷(xiāo)太大。啟用 tracemalloc 模塊后,能夠提供有關(guān)錯誤來(lái)源的一些額外信息。例如,ResourceWarning 記錄了資源分配的跟蹤信息,而緩沖區溢出錯誤記錄了內存塊分配的跟蹤信息。

Python 開(kāi)發(fā)模式不會(huì )阻止命令行參數 -O 刪除 assert 語(yǔ)句,也不會(huì )阻止將 __debug__ 設為 False。

Python 開(kāi)發(fā)模式只能在 Python 啟動(dòng)時(shí)啟用。其參數值可從 sys.flags.dev_mode 讀取。

在 3.8 版更改: 現在, io.IOBase 的析構函數會(huì )記錄 close() 觸發(fā)的異常。

在 3.9 版更改: 現在,字符串編碼和解碼操作時(shí)會(huì )檢查 encodingerrors 參數。

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()

上述代碼沒(méi)有顯式關(guān)閉文件。默認情況下,Python 不會(huì )觸發(fā)任何警告。下面用 README.txt 文件測試下,有 269 行:

$ python3 script.py README.txt
269

啟用 Python 開(kāi)發(fā)模式后,則會(huì )顯示一條 ResourceWarning 警告:

$ python3 -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 后,則還會(huì )顯示打開(kāi)文件的那行代碼:

$ python3 -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])

修正方案就是顯式關(guān)閉文件。下面用上下文管理器作為示例:

def main():
    # Close the file explicitly when exiting the with block
    with open(sys.argv[1]) as fp:
        nlines = len(fp.readlines())
    print(nlines)

未能顯式關(guān)閉資源,會(huì )讓資源打開(kāi)時(shí)長(cháng)遠超預期;在退出 Python 時(shí)可能會(huì )導致嚴重問(wèn)題。這在 CPython 中比較糟糕,但在 PyPy 中會(huì )更糟。顯式關(guān)閉資源能讓?xiě)贸绦蚋臃€定可靠。

文件描述符錯誤示例?

顯示自身的第一行代碼:

import os

def main():
    fp = open(__file__)
    firstline = fp.readline()
    print(firstline.rstrip())
    os.close(fp.fileno())
    # The file is closed implicitly

main()

默認情況下,Python 不會(huì )觸發(fā)任何警告:

$ python3 script.py
import os

在 Python 開(kāi)發(fā)模式下,會(huì )在析構文件對象時(shí)顯示 ResourceWarning 并記錄 “Bad file descriptor” 錯誤。

$ python3 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()) 會(huì )關(guān)閉文件描述符。當文件對象析構函數試圖再次關(guān)閉文件描述符時(shí)會(huì )失敗,并觸發(fā) Bad file descriptor 錯誤。每個(gè)文件描述符只允許關(guān)閉一次。在最壞的情況下,關(guān)閉兩次會(huì )導致程序崩潰(示例可參見(jiàn) bpo-18748 )。

修正方案是刪除 os.close(fp.fileno()) 這一行,或者打開(kāi)文件時(shí)帶上 closefd=False 參數。