調試器是一個程式語言非常重要的部分,調試器是一種用於診斷和修復程式碼錯誤(或稱為bug)的工具,它允許開發者在程式執行時逐步查看和分析程式碼的狀態和行為,它可以幫助開發者診斷和修復程式碼錯誤,理解程式的行為,優化效能。調試器在任何程式語言中都是一個非常強大的工具,可以提高開發效率和程式碼品質。
讓程式停下來
如果我們需要對一個程式進行調試最重要的一個點就是如果讓程式停下來,只有讓程式的執行停下來我們才能夠觀察程式執行的狀態,例如我們需要調試99 乘法表:
def m99(): for i in range(1, 10): for j in range(1, i + 1): print(f"{i}x{j}={i*j}", end='\t') print() if __name__ == '__main__': m99()
現在執行命令python -m pdb pdbusage.py
就可以對上面的程式進行調試:
#(py3.8) ➜ pdb_test git:(master) ✗ python -m pdb pdbusage.py
> /Users/xxxx/Desktop/workdir/dive-into-cpython/code/pdb_test/pdbusage.py(3 )()
-> def m99():
(Pdb) s
> /Users/xxxx/Desktop/workdir/dive-into-cpython/code/pdb_test/pdbusage .py(10)()
-> if __name__ == '__main__':
(Pdb) s
> /Users/xxxx/Desktop/workdir/dive-into- cpython/code/pdb_test/pdbusage.py(11)()
-> m99()
(Pdb) s
--Call--
> /Users/ xxxx/Desktop/workdir/dive-into-cpython/code/pdb_test/pdbusage.py(3)m99()
-> def m99():
(Pdb) s
> /Users /xxxx/Desktop/workdir/dive-into-cpython/code/pdb_test/pdbusage.py(4)m99()
-> for i in range(1, 10):
(Pdb) s
> /Users/xxxx/Desktop/workdir/dive-into-cpython/code/pdb_test/pdbusage.py(5)m99()
-> for j in range(1, i 1):
(Pdb) s
> /Users/xxxx/Desktop/workdir/dive-into-cpython/code/pdb_test/pdbusage.py(6)m99()
-> print(f"{ i}x{j}={i*j}", end='\t')
(Pdb) p i
1
(Pdb)
當然你也可以在IDE 當中進行調試:
根據我們的調試經驗容易知道,要調試一個程式首先最重要的一點就是程式需要在我們設定斷點的位置要能夠停下來
cpython 王炸機制—— tracing
現在的問題是,上面的程式是怎麼在程式執行時停下來的呢?
根據前面的學習我們可以了解到,一個python 程式的執行首先需要經過python 編譯器編譯成python 字節碼,然後交給python 虛擬機進行執行,如果需要程式停下來就一定需要虛擬機器給上層的python 程式一個接口,讓程式在執行的時候可以知道現在執行到什麼位置了。這個神秘的機制就隱藏在 sys 這個模組當中,事實上這個模組幾乎承擔了所有我們與 python 解釋器互動的介面。實現調試器一個非常重要的函數就是sys.settrace 函數,這個函數將為線程設定一個追蹤函數,當虛擬機有函數調用,執行完一行程式碼的時候、甚至執行完一條字節碼之後就會執行這個函數。
實作一個 Python 原始碼偵錯器,需要在系統中設定追蹤函數。該函數是線程特定的;為了支援多線程調試,必須對每個正在調試的線程註冊一個追蹤函數,使用 settrace() 或使用 threading.settrace() 。
跟蹤函式應該有三個參數:frame、event 和 arg。 frame 是當前的堆疊幀。 event 是一個字串:'call'、'line'、'return'、'exception'、 'opcode' 、'c_call' 或 'c_exception'。 arg 取決於事件類型。
追蹤函數在每次進入新的局部作用域時被呼叫(事件設定為'call');它應該傳回一個引用,用於新作用域的本機追蹤函數,或者如果不想在該作用域中進行跟踪,則返回None。
如果在追蹤函數中發生任何錯誤,它將被取消設置,就像呼叫settrace(None)一樣。
事件的意義如下:
call,呼叫了一個函數(或進入了其他程式碼區塊)。呼叫本機追蹤函數時,指定了 arg 為 None,並在傳回值中指定了本機函數。
line,將要執行一行新的程式碼,參數 arg 的值為 None 。
return,函數(或其他程式碼區塊)即將返回。當事件由異常引起時,本地追蹤函數被呼叫並傳回arg值為None。追蹤函數的返回值將被忽略。
exception,發生了異常。呼叫本機追蹤函數;arg是一個元組(exception,value,traceback);傳回值指定了新的本機追蹤函數。
opcode,解释器即将执行新的字节码指令。执行本地追踪函数,arg为空,返回一个新的本地追踪函数。默认情况下,不会发出每个操作码的事件:必须通过在帧上设置 f_trace_opcodes 为 True 来显式请求。
c_call,一个 c 函数将要被调用。
c_exception,调用 c 函数的时候产生了异常。
自己动手实现一个简单的调试器
我们将在此章节中实现一个简单的调试器,以帮助大家理解调试器的实现原理。调试器的实现代码如下所示,只有短短几十行却可以帮助我们深入去理解调试器的原理,我们先看一下实现的效果在后文当中再去分析具体的实现:
import sys file = sys.argv[1] with open(file, "r+") as fp: code = fp.read() lines = code.split("\n") def do_line(frame, event, arg): print("debugging line:", lines[frame.f_lineno - 1]) return debug def debug(frame, event, arg): if event == "line": while True: _ = input("(Pdb)") if _ == 'n': return do_line(frame, event, arg) elif _.startswith('p'): _, v = _.split() v = eval(v, frame.f_globals, frame.f_locals) print(v) elif _ == 'q': sys.exit(0) return debug if __name__ == '__main__': sys.settrace(debug) exec(code, None, None) sys.settrace(None)
在上面的程序当中使用如下:
输入 n 执行一行代码。
p name 打印变量 name 。
q 退出调试。
现在我们执行上面的程序,进行程序调试:
(py3.10) ➜ pdb_test git:(master) ✗ python mydebugger.py pdbusage.py
(Pdb)n
debugging line: def m99():
(Pdb)n
debugging line: if __name__ == '__main__':
(Pdb)n
debugging line: m99()
(Pdb)n
debugging line: for i in range(1, 10):
(Pdb)n
debugging line: for j in range(1, i + 1):
(Pdb)n
debugging line: print(f"{i}x{j}={i*j}", end='\t')
1x1=1 (Pdb)n
debugging line: for j in range(1, i + 1):
(Pdb)p i
1
(Pdb)p j
1
(Pdb)q
(py3.10) ➜ pdb_test git:(master) ✗
可以看到我们的程序真正的被调试起来了。
现在我们来分析一下我们自己实现的简易版本的调试器,在前文当中我们已经提到了 sys.settrace 函数,调用这个函数时需要传递一个函数作为参数,被传入的函数需要接受三个参数:
frame,当前正在执行的栈帧。
event,事件的类别,这一点在前面的文件当中已经提到了。
arg,参数这一点在前面也已经提到了。
同时需要注意的是这个函数也需要有一个返回值,python 虚拟机在下一次事件发生的时候会调用返回的这个函数,如果返回 None 那么就不会在发生事件的时候调用 tracing 函数了,这是代码当中为什么在 debug 返回 debug 的原因。
我们只对 line 这个事件进行处理,然后进行死循环,只有输入 n 指令的时候才会执行下一行,然后打印正在执行的行,这个时候就会退出函数 debug ,程序就会继续执行了。python 内置的 eval 函数可以获取变量的值。
python 官方调试器源码分析
python 官方的调试器为 pdb 这个是 python 标准库自带的,我们可以通过 python -m pdb xx.py
去调试文件 xx.py 。这里我们只分析核心代码:
代码位置:bdp.py 下面的 Bdb 类
def run(self, cmd, globals=None, locals=None): """Debug a statement executed via the exec() function. globals defaults to __main__.dict; locals defaults to globals. """ if globals is None: import __main__ globals = __main__.__dict__ if locals is None: locals = globals self.reset() if isinstance(cmd, str): cmd = compile(cmd, "<string>", "exec") sys.settrace(self.trace_dispatch) try: exec(cmd, globals, locals) except BdbQuit: pass finally: self.quitting = True sys.settrace(None)
上面的函数主要是使用 sys.settrace 函数进行 tracing 操作,当有事件发生的时候就能够捕捉了。在上面的代码当中 tracing 函数为 self.trace_dispatch 我们再来看这个函数的代码:
def trace_dispatch(self, frame, event, arg): """Dispatch a trace function for debugged frames based on the event. This function is installed as the trace function for debugged frames. Its return value is the new trace function, which is usually itself. The default implementation decides how to dispatch a frame, depending on the type of event (passed in as a string) that is about to be executed. The event can be one of the following: line: A new line of code is going to be executed. call: A function is about to be called or another code block is entered. return: A function or other code block is about to return. exception: An exception has occurred. c_call: A C function is about to be called. c_return: A C function has returned. c_exception: A C function has raised an exception. For the Python events, specialized functions (see the dispatch_*() methods) are called. For the C events, no action is taken. The arg parameter depends on the previous event. """ if self.quitting: return # None if event == 'line': print("In line") return self.dispatch_line(frame) if event == 'call': print("In call") return self.dispatch_call(frame, arg) if event == 'return': print("In return") return self.dispatch_return(frame, arg) if event == 'exception': print("In execption") return self.dispatch_exception(frame, arg) if event == 'c_call': print("In c_call") return self.trace_dispatch if event == 'c_exception': print("In c_exception") return self.trace_dispatch if event == 'c_return': print("In c_return") return self.trace_dispatch print('bdb.Bdb.dispatch: unknown debugging event:', repr(event)) return self.trace_dispatch
从上面的代码当中可以看到每一种事件都有一个对应的处理函数,在本文当中我们主要分析 函数 dispatch_line,这个处理 line 事件的函数。
def dispatch_line(self, frame): """Invoke user function and return trace function for line event. If the debugger stops on the current line, invoke self.user_line(). Raise BdbQuit if self.quitting is set. Return self.trace_dispatch to continue tracing in this scope. """ if self.stop_here(frame) or self.break_here(frame): self.user_line(frame) if self.quitting: raise BdbQuit return self.trace_dispatch
这个函数首先会判断是否需要在当前行停下来,如果需要停下来就需要进入 user_line 这个函数,后面的调用链函数比较长,我们直接看最后执行的函数,根据我们使用 pdb 的经验来看,最终肯定是一个 while 循环让我们可以不断的输入指令进行处理:
def cmdloop(self, intro=None): """Repeatedly issue a prompt, accept input, parse an initial prefix off the received input, and dispatch to action methods, passing them the remainder of the line as argument. """ print("In cmdloop") self.preloop() if self.use_rawinput and self.completekey: try: import readline self.old_completer = readline.get_completer() readline.set_completer(self.complete) readline.parse_and_bind(self.completekey+": complete") except ImportError: pass try: if intro is not None: self.intro = intro print(f"{self.intro = }") if self.intro: self.stdout.write(str(self.intro)+"\n") stop = None while not stop: print(f"{self.cmdqueue = }") if self.cmdqueue: line = self.cmdqueue.pop(0) else: print(f"{self.prompt = } {self.use_rawinput}") if self.use_rawinput: try: # 核心逻辑就在这里 不断的要求输入然后进行处理 line = input(self.prompt) # self.prompt = '(Pdb)' except EOFError: line = 'EOF' else: self.stdout.write(self.prompt) self.stdout.flush() line = self.stdin.readline() if not len(line): line = 'EOF' else: line = line.rstrip('\r\n') line = self.precmd(line) stop = self.onecmd(line) # 这个函数就是处理我们输入的字符串的比如 p n 等等 stop = self.postcmd(stop, line) self.postloop() finally: if self.use_rawinput and self.completekey: try: import readline readline.set_completer(self.old_completer) except ImportError: pass
def onecmd(self, line): """Interpret the argument as though it had been typed in response to the prompt. This may be overridden, but should not normally need to be; see the precmd() and postcmd() methods for useful execution hooks. The return value is a flag indicating whether interpretation of commands by the interpreter should stop. """ cmd, arg, line = self.parseline(line) if not line: return self.emptyline() if cmd is None: return self.default(line) self.lastcmd = line if line == 'EOF' : self.lastcmd = '' if cmd == '': return self.default(line) else: try: # 根据下面的代码可以分析了解到如果我们执行命令 p 执行的函数为 do_p func = getattr(self, 'do_' + cmd) except AttributeError: return self.default(line) return func(arg)
现在我们再来看一下 do_p 打印一个表达式是如何实现的:
def do_p(self, arg): """p expression Print the value of the expression. """ self._msg_val_func(arg, repr) def _msg_val_func(self, arg, func): try: val = self._getval(arg) except: return # _getval() has displayed the error try: self.message(func(val)) except: self._error_exc() def _getval(self, arg): try: # 看到这里就破案了这不是和我们自己实现的 pdb 获取变量的方式一样嘛 都是 # 使用当前执行栈帧的全局和局部变量交给 eval 函数处理 并且将它的返回值输出 return eval(arg, self.curframe.f_globals, self.curframe_locals) except: self._error_exc() raise
以上是Python虛擬機器中調試器的實作原理是什麼的詳細內容。更多資訊請關注PHP中文網其他相關文章!

每天學習Python兩個小時是否足夠?這取決於你的目標和學習方法。 1)制定清晰的學習計劃,2)選擇合適的學習資源和方法,3)動手實踐和復習鞏固,可以在這段時間內逐步掌握Python的基本知識和高級功能。

Python在Web開發中的關鍵應用包括使用Django和Flask框架、API開發、數據分析與可視化、機器學習與AI、以及性能優化。 1.Django和Flask框架:Django適合快速開發複雜應用,Flask適用於小型或高度自定義項目。 2.API開發:使用Flask或DjangoRESTFramework構建RESTfulAPI。 3.數據分析與可視化:利用Python處理數據並通過Web界面展示。 4.機器學習與AI:Python用於構建智能Web應用。 5.性能優化:通過異步編程、緩存和代碼優

Python在開發效率上優於C ,但C 在執行性能上更高。 1.Python的簡潔語法和豐富庫提高開發效率。 2.C 的編譯型特性和硬件控制提升執行性能。選擇時需根據項目需求權衡開發速度與執行效率。

Python在現實世界中的應用包括數據分析、Web開發、人工智能和自動化。 1)在數據分析中,Python使用Pandas和Matplotlib處理和可視化數據。 2)Web開發中,Django和Flask框架簡化了Web應用的創建。 3)人工智能領域,TensorFlow和PyTorch用於構建和訓練模型。 4)自動化方面,Python腳本可用於復製文件等任務。

Python在數據科學、Web開發和自動化腳本領域廣泛應用。 1)在數據科學中,Python通過NumPy、Pandas等庫簡化數據處理和分析。 2)在Web開發中,Django和Flask框架使開發者能快速構建應用。 3)在自動化腳本中,Python的簡潔性和標準庫使其成為理想選擇。

Python的靈活性體現在多範式支持和動態類型系統,易用性則源於語法簡潔和豐富的標準庫。 1.靈活性:支持面向對象、函數式和過程式編程,動態類型系統提高開發效率。 2.易用性:語法接近自然語言,標準庫涵蓋廣泛功能,簡化開發過程。

Python因其簡潔與強大而備受青睞,適用於從初學者到高級開發者的各種需求。其多功能性體現在:1)易學易用,語法簡單;2)豐富的庫和框架,如NumPy、Pandas等;3)跨平台支持,可在多種操作系統上運行;4)適合腳本和自動化任務,提升工作效率。

可以,在每天花費兩個小時的時間內學會Python。 1.制定合理的學習計劃,2.選擇合適的學習資源,3.通過實踐鞏固所學知識,這些步驟能幫助你在短時間內掌握Python。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

PhpStorm Mac 版本
最新(2018.2.1 )專業的PHP整合開發工具

SublimeText3 Linux新版
SublimeText3 Linux最新版

VSCode Windows 64位元 下載
微軟推出的免費、功能強大的一款IDE編輯器

ZendStudio 13.5.1 Mac
強大的PHP整合開發環境

記事本++7.3.1
好用且免費的程式碼編輯器