Python 开发模式¶
3.7 版新加入.
Python 开发模式引入了额外的运行时检查,由于成本太高,所以默认情况下是不启用的。如果代码是正确的,它不应该比默认情况的更加详细;只有在检测到问题的时候才会发出新的警告。
它可以通过 -X dev
命令行选项或通过设置 PYTHONDEVMODE
环境变量为 1
来启用。
Python 开发模式的效果¶
启用 Python 开发模式后的效果,与以下命令类似,不过还有下面的额外效果:
PYTHONMALLOC=debug PYTHONASYNCIODEBUG=1 python3 -W default -X faulthandler
Python 开发模式的效果:
加入
default
warning filter 。下述警告信息将会显示出来:通常上述警告是由默认的 warning filters 负责处理的。
其行为相当于使用了
-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 debug mode。比如
asyncio
会检查没有等待的协程并记录下来。效果如同将环境变量
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
。
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 行。
$ python3 script.py README.txt
269
启用 Python 开发模式后,则会显示一条 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
后,则还会显示打开文件的那行代码:
$ 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])
修正方案就是显式关闭文件。下面用上下文管理器作为示例:
def main():
# Close the file explicitly when exiting the with block
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())
# The file is closed implicitly
main()
默认情况下,Python 不会触发任何警告:
$ python3 script.py
import os
在 Python 开发模式下,会在析构文件对象时显示 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())
会关闭文件描述符。当文件对象析构函数试图再次关闭文件描述符时会失败,并触发 Bad file descriptor
错误。每个文件描述符只允许关闭一次。在最坏的情况下,关闭两次会导致程序崩溃(示例可参见 bpo-18748 )。
修正方案是删除 os.close(fp.fileno())
这一行,或者打开文件时带上 closefd=False
参数。