breakpoint() <Python 內(nèi)置函數(shù)>

轉(zhuǎn)載須注明出處:簡書@Orca_J35 | GitHub@orca-j35

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

目錄.jpg

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ù)簽名匹配。

pudb.png

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)試器:

web-pdb.jpg

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

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

sys._getframe([depth])

從調(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. 參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容