breakpoint() 是 Python 3.7 中新增加的內(nèi)置函數(shù),本文介紹了該函數(shù)的使用方法,目錄結(jié)構(gòu)如下:

1. how2 Implementation
雖然 breakpoint 是由 C 語言實(shí)現(xiàn)的 ,但我們可以使用 Python pseudo-code 來描述其實(shí)現(xiàn)過程:
# In builtins.
def breakpoint(*args, **kws):
import sys
missing = object()
hook = getattr(sys, 'breakpointhook', missing)
if hook is missing:
raise RuntimeError('lost sys.breakpointhook')
return hook(*args, **kws)
# In sys.
def breakpointhook(*args, **kws):
import importlib, os, warnings
hookname = os.getenv('PYTHONBREAKPOINT')
if hookname is None or len(hookname) == 0:
hookname = 'pdb.set_trace'
elif hookname == '0':
return None
modname, dot, funcname = hookname.rpartition('.')
if dot == '':
modname = 'builtins'
try:
module = importlib.import_module(modname)
hook = getattr(module, funcname)
except:
warnings.warn(
'Ignoring unimportable $PYTHONBREAKPOINT: {}'.format(
hookname),
RuntimeWarning)
return hook(*args, **kws)
__breakpointhook__ = breakpointhook
2. how2 work
在未設(shè)置 PYTHONBREAKPOINT 的情況下,breakpoint() 會(huì)中斷當(dāng)前程序并進(jìn)入 pdb 調(diào)試器。具體工作方式請(qǐng)查看相應(yīng)子章節(jié)。
2.1 breakpoint()
breakpoint(*args, **kws)
偽代碼:
# In builtins.
def breakpoint(*args, **kws):
import sys
missing = object()
# 設(shè)置鉤子函數(shù)
hook = getattr(sys, 'breakpointhook', missing)
if hook is missing:
raise RuntimeError('lost sys.breakpointhook')
# 返回鉤子函數(shù)的調(diào)用
return hook(*args, **kws)
可見 breakpoint 的工作僅是設(shè)置并調(diào)用 hook 函數(shù),這里只需要注意以下幾點(diǎn):
-
breakpoint中的 hook 變量將引用sys.breakpointhook函數(shù)對(duì)象; -
breakpoint會(huì)將自己所有的實(shí)參都傳遞給sys.breakpointhook(); - 如果
sys.breakpointhook缺失,則會(huì)拋出RuntimeError
2.2 breakpointhook()
sys.breakpointhook(*args, **kws)
偽代碼:
# In sys.
def breakpointhook(*args, **kws):
import importlib, os, warnings
hookname = os.getenv('PYTHONBREAKPOINT')
if hookname is None or len(hookname) == 0:
hookname = 'pdb.set_trace'
elif hookname == '0':
return None
modname, dot, funcname = hookname.rpartition('.')
if dot == '':
modname = 'builtins'
try:
# 模塊導(dǎo)入失敗,或funcname不可調(diào)用都會(huì)引發(fā)RuntimeWarning
module = importlib.import_module(modname)
hook = getattr(module, funcname)
except:
# 如果拋出異常,則不會(huì)執(zhí)行hook(*args, **kws)
warnings.warn(
'Ignoring unimportable $PYTHONBREAKPOINT: {}'.format(
hookname),
RuntimeWarning)
# 如果實(shí)參與函數(shù)簽名中的參數(shù)不匹配,則會(huì)拋出TypeError
return hook(*args, **kws)
__breakpointhook__ = breakpointhook
sys.breakpointhook 作為 breakpoint 的鉤子函數(shù),是正真實(shí)現(xiàn)具體功能的地方。
sys.breakpointhook 會(huì)訪問環(huán)境變量 PYTHONBREAKPOINT,從而確定 hook 的引用對(duì)象,也就是說 PYTHONBREAKPOINT 的狀態(tài)對(duì)執(zhí)行結(jié)果有決定性的作用。
具體而言,PYTHONBREAKPOINT 存在如下幾種狀態(tài):
-
完全不設(shè)置該環(huán)境變量:此時(shí),hook 會(huì)引用
pdb.set_trace,所以最終會(huì)進(jìn)入pdb調(diào)試器。(提示,由于pdb.set_trace(*, header=None)只接受關(guān)鍵字參數(shù)header,因此不要向breakpoint()傳遞任何其它參數(shù)) -
PYTHONBREAKPOINT=:此時(shí),環(huán)境變量的值為空字符串,這與完全不設(shè)置該環(huán)境變量的效果相同。 -
PYTHONBREAKPOINT=0:此時(shí),breakpointhook會(huì)立即返回None,從而禁用調(diào)試; -
PYTHONBREAKPOINT=some.importable.callable:此時(shí),breakpointhook()將導(dǎo)入some.importable模塊,然后通過 hook 引用模塊中的callable對(duì)象 -
PYTHONBREAKPOINT=callable:此時(shí),callable表示一個(gè)內(nèi)置可調(diào)用對(duì)象,如PYTHONBREAKPOINT=print。
每次調(diào)用 sys.breakpointhook() 時(shí),都會(huì)訪問 PYTHONBREAKPOINT 變量。如果在程序執(zhí)行期間改變了 PYTHONBREAKPOINT 的值, sys.breakpointhook() 便會(huì)讀取變化后的值。因此,程序可以執(zhí)行如下操作:
os.environ['PYTHONBREAKPOINT'] = 'foo.bar.baz'
breakpoint() # Imports foo.bar and calls foo.bar.baz()
注意:如果在解釋器啟動(dòng)時(shí)伴隨參數(shù) -E,便會(huì)忽略所有 PYTHON* 環(huán)境變量(PYTHONBREAKPOINT 也不列外)。這意味著breakpoint() 會(huì)遵守默認(rèn)行為,即中斷當(dāng)前程序并進(jìn)入 pdb 調(diào)試器。
2.3 __breakpointhook__
# In sys.
__breakpointhook__ = breakpointhook
在初始化時(shí) ,__breakpointhook__ 和 breakpointhook 會(huì)引用相同的函數(shù)對(duì)象。因此,就算重寫了 breakpointhook 函數(shù),也可通過 __breakpointhook__ 將其重置:
# 重置 breakpointhook
sys.breakpointhook = sys.__breakpointhook__
sys.breakpointhook / sys.__breakpointhook__ 工作方式與 sys.displayhook() / sys.__displayhook__ 和 sys.excepthook() / sys.__excepthook__ 完全相同。
3. with pdb.set_trace
在使用 pdb 調(diào)試器時(shí),我們通常會(huì)這樣設(shè)置斷點(diǎn):
def divide(e, f):
# 設(shè)置一個(gè)斷點(diǎn),執(zhí)行至此處后,便會(huì)中斷當(dāng)前程序并進(jìn)入pdb調(diào)試器。
import pdb; pdb.set_trace()
return f / e
在 Python 3.7 中,我們可以通過 breakpoint() 函數(shù)來設(shè)置斷點(diǎn)(前提是未定義 PYTHONBREAKPOINT環(huán)境變量,或該環(huán)境變量的值等于空字符串)。
"""file_name:bugs.py"""
def divide(e, f):
breakpoint(header="進(jìn)入調(diào)試器")
return e / f
a, b = 1, 9
print(divide(a, b))
此時(shí),breakpointhook() 會(huì)返回 pdb.set_trace() ,從而進(jìn)入 pdb 調(diào)試器。
Tips:在 Python 3.7 中,pdb.set_trace 僅支持關(guān)鍵字參數(shù) header,不接受其他任何參數(shù)。如果傳遞了錯(cuò)誤的參數(shù),便會(huì)引發(fā) TypeError 。
bugs.py 的執(zhí)行效果如下:
$ python.exe bugs.py
進(jìn)入調(diào)試器
> c:\users\iwhal\desktop\pytest\bugs.py(3)divide()
-> return e / f
(Pdb) p a+b
10
(Pdb) c
0.1111111111111111
Tips:命令 p 用于查看表達(dá)式的值;命令 c 用于退出調(diào)試器,并繼續(xù)執(zhí)行程序。
4. with pudb.set_trace
通過 PYTHONBREAKPOINT 還可選用別的調(diào)試器。例如,當(dāng)我們想要使用 PuDB (一個(gè)基于控制臺(tái)的可視化調(diào)試器)時(shí),只需修改 PYTHONBREAKPOINT :
$ PYTHONBREAKPOINT=pudb.set_trace python3.7 bugs.py
注意:這里需要手動(dòng)安裝 pudb (pip install pudb),并且 pudb 僅支持類 UNIX 環(huán)境。由于我是 windows 環(huán)境,所以無法進(jìn)行更詳細(xì)的演示。
總之,通過修改PYTHONBREAKPOINT 的值,我們可以選擇自己需要的調(diào)試器。但是同樣需要注意的是:通過 breakpoint 傳遞的參數(shù),是否與調(diào)試器的函數(shù)簽名匹配。

5. with web_pdb.set_trace
如果想要使用 Web-PDB (Web-PDB 在內(nèi)置 PDB 上增加了 web 界面,并允許在 web 瀏覽器中遠(yuǎn)程調(diào)試 Python 腳本),同樣只需修改 PYTHONBREAKPOINT。注意:這里需要手動(dòng)安裝 web-pdb (pip install web-pdb)。
為了方便演示,先創(chuàng)建一個(gè)小腳本:
"""file_name:test.py"""
a, b = 1, 2
breakpoint()
a, b = b, a
修改 PYTHONBREAKPOINT ,并執(zhí)行腳本:
$ PYTHONBREAKPOINT=web_pdb.set_trace python.exe test.py
2018-08-23 10:54:05,108: root - web_console:110 - CRITICAL - Web-PDB: starting web-server on LAPTOP-AR0R702R:5555...
之后便可通過瀏覽器,在 5555 端口上進(jìn)入 Web-PDB 調(diào)試器:

6. with IPython.embed
breakpoint 函數(shù)不僅適用于調(diào)試器,也適用于任何可調(diào)用對(duì)象,只要參數(shù)匹配即可。因此,當(dāng)我們想要在程序執(zhí)行過程中,啟動(dòng)一個(gè)交互式 shell 時(shí)(比如 IPython),同樣只需修改 PYTHONBREAKPOINT,如下:
$ PYTHONBREAKPOINT=IPython.embed python3.7 bugs.py
Python 3.7.0 (v3.7.0:1bf9cc5093, Jun 27 2018, 04:59:51) [MSC v.1914 64 bit (AMD64)]
Type 'copyright', 'credits' or 'license' for more information
IPython 6.5.0 -- An enhanced Interactive Python. Type '?' for help.
進(jìn)入調(diào)試器
In [1]: a,b
Out[1]: (1, 9)
In [2]: quit()
0.1111111111111111
6.1 what's IPython.embed
IPython.embed(**kwargs) 會(huì)在程序的當(dāng)前運(yùn)行位置嵌入 IPython。在第一次調(diào)用該方法時(shí),會(huì)先創(chuàng)建 InteractiveShellEmbed 類的一個(gè)實(shí)例,并調(diào)用該實(shí)例。再次調(diào)用該方法時(shí),會(huì)直接調(diào)用之前創(chuàng)建的 InteractiveShellEmbed 實(shí)例。默認(rèn)情況下,InteractiveShellEmbed 實(shí)例和當(dāng)前程序使用相同的命名空間。另外,IPython.embed 允許我們?cè)谇度朦c(diǎn)打印指定字符串。
"""file_name:test.py"""
from IPython import embed
a = 10
b = 20
embed(header='First time')# 會(huì)在嵌入點(diǎn)打印該字符串
c = 30
d = 40
embed()
執(zhí)行 test.py 腳本后,會(huì)分兩次進(jìn)入 IPython:
$ C:/Python37/python.exe c:/Users/iwhal/Desktop/PyTest/test.py
Python 3.7.0 (v3.7.0:1bf9cc5093, Jun 27 2018, 04:59:51) [MSC v.1914 64 bit (AMD64)]
Type 'copyright', 'credits' or 'license' for more information
IPython 6.5.0 -- An enhanced Interactive Python. Type '?' for help.
First time
In [1]: a,b
Out[1]: (10, 20)
In [2]: quit()
Python 3.7.0 (v3.7.0:1bf9cc5093, Jun 27 2018, 04:59:51) [MSC v.1914 64 bit (AMD64)]
Type 'copyright', 'credits' or 'license' for more information
IPython 6.5.0 -- An enhanced Interactive Python. Type '?' for help.
In [1]: a,b,c,d
Out[1]: (10, 20, 30, 40)
In [2]: quit()
7. with Functions
breakpoint 函數(shù)不僅適用于調(diào)試器,也適用于任何可調(diào)用對(duì)象。因此,我們可以通過 breakpoint 調(diào)用自定義函數(shù),只要參數(shù)匹配即可。。
如下代碼將創(chuàng)建一個(gè)可打印本地作用域內(nèi)所有變量的函數(shù):
"""file_name:bp_utils.py"""
from pprint import pprint
import sys
def print_locals(header=None):
print(header)
caller = sys._getframe(1) # Caller is 1 frame up.
pprint(caller.f_locals)
只要合理設(shè)置 PYTHONBREAKPOINT 便可調(diào)用該函數(shù):
$ PYTHONBREAKPOINT=bp_utils.print_locals python.exe bugs.py
First time
{'e': 1, 'f': 9}
0.1111111111111111
基于同樣的思路,我們還可以調(diào)用內(nèi)置函數(shù),比如 print 函數(shù)。
先創(chuàng)建一個(gè)腳本,用于向內(nèi)置傳遞傳輸,只需包含 breakpoint 即可:
"""file_name:built_in.py"""
breakpoint("hello")
執(zhí)行該腳本:
$ PYTHONBREAKPOINT=print python3.7 built_in.py
hello
7.1 what's sys._getframe
從調(diào)用棧(call stack)中返回一個(gè)幀(frame)對(duì)象。如果給出了 depth 參數(shù)(需是整數(shù)),則會(huì)返回棧頂下方對(duì)應(yīng)深度的幀對(duì)象。如果 depth 值超過了調(diào)用棧的深度,將拋出 ValueError 異常。depth 的默認(rèn)值是 0,此時(shí)會(huì)返回調(diào)用棧最頂部的幀。
CPython implementation detail: 該函數(shù)僅用于內(nèi)部和專門用途。并不保證在所有 Python 實(shí)現(xiàn)中都存在。
8. 參考
- PEP 553 – Built-in breakpoint()
- Python 3.7 中的新特性:編譯自 Cool New Features in Python 3.7
- Python 3.7’s new builtin breakpoint?—?a quick tour
breakpoint()- Built-in Functions- Built-in breakpoint() - What's New In Python 3.7
-
sys.breakpointhook() - 30.1.
sys— System-specific parameters and functions -
sys.__breakpointhook__ - 30.1.
sys— System-specific parameters and functions - PYTHONBREAKPOINT - 1. Command line and environment
- ipython-embed
pdb.set_trace()