大多數一般的 Python 除錯器只支援除錯 Python 程式碼,但開發者常用 Python 搭配 C 或 C++ 來除錯。 使用混合程式碼的場景包括需要高效能或能直接呼叫平台 API 的應用程式,通常以 Python 及 C 或 C++ 編寫。
Visual Studio 提供整合且同時的混合模式除錯,適用於 Python 與原生 C/C++ 程式碼。 當你在 Visual Studio 安裝程式中選擇 Python 原生開發工具選項時,即可獲得支援:
在本文中,您將探討如何運用以下混合模式除錯功能:
- 合併呼叫堆疊
- 介於 Python 與原生程式碼之間的步驟
- 兩種程式碼中的斷點
- 查看 Python 物件在原生框架中的表示,或反之查看原生框架中的物件在 Python 中的表示。
- Python 專案或 C++ 專案背景下的除錯
先決條件
Visual Studio 2017 及以後版本。 在 Visual Studio 2015 及更早版本的 Python Tools for Visual Studio 1.x 中,無法支援混合模式除錯。
Visual Studio 已安裝並支援 Python 工作負載。 如需詳細資訊,請參閱 在Visual Studio中安裝 Python 支援。
在 Python 專案中啟用混合模式除錯
以下步驟說明如何在 Python 專案中啟用混合模式除錯:
在 [方案總管] 中,以滑鼠右鍵按兩下 Python 專案,然後選取 [ 屬性]。
在屬性面板中,選擇除錯標籤,然後選擇除錯>啟用原生程式碼除錯選項:
此選項可啟用所有除錯會話的混合模式。
小提示
當您啟用機器碼偵錯時,Python 輸出視窗可能會在程式完成之後立即關閉,而不會暫停並顯示 [按任何按鍵繼續 提示]。 若要在啟用原生程式碼偵錯之後強制暫停並提示,請將參數新增
-i至 [執行>解釋器參數] 字段上的 [偵錯] 索引標籤。這個參數會在程式碼執行之後,讓 Python 解釋器進入互動式模式。 程式會等候您選取 ctrl+Z+Enter 來關閉視窗。選取 [檔案>儲存 ] (或 Ctrl+S) 以儲存屬性變更。
若要將混合模式除錯器附加到現有程序,請選擇 除錯>附加至程序。 對話開始了。
在 「附加到流程 」對話框中,從列表中選擇相應的流程。
在「 附加」 欄位,請使用 「選擇 」選項開啟 「選擇程式碼類型 」對話框。
在 「選擇程式碼類型 」對話框中,選擇 「除錯這些程式碼類型 」選項。
在清單中,選擇 Python(原生)勾選框,並選擇 OK:
選擇 Attach 以啟動除錯器。
程式碼類型設定是持久的。 如果你想關閉混合模式除錯並稍後附加到其他程序,請清除 Python(原生) 程式碼類型勾選框,並選擇 原生 程式碼類型勾選框。
你可以選擇其他程式碼類型,無論是作為對原生選項的補充還是替代。 舉例來說,如果一個受管理的應用程式托管 CPython,而 CPython 又使用原生擴充模組,而你想除錯這三個程式碼專案,請選擇 Python、 Native 和 Managed 的勾選框。 這種方式提供了統一的除錯體驗,包含合併呼叫堆疊及在三個執行環境之間的步進。
使用虛擬環境工作
當你使用這種混合模式偵錯虛擬環境(venvs)的方法時,Windows 版 Python 會使用一個 python.exe stub 檔案,Visual Studio 將其找到並載入為子進程。
在 Python 3.8 及以後版本,混合模式不支援多程序除錯。 當你開始除錯時,除錯的是 stub 子程序,而不是應用程式。 對於附加情境,解決方法是附上正確的
python.exe檔案。 當你啟動除錯應用程式(例如透過 F5 鍵快捷鍵)時,你可以用指令C:\Python310-64\python.exe -m venv venv --symlinks建立你的 venv。 在指令中插入你偏好的 Python 版本。 預設情況下,只有管理員可以在 Windows 上建立符號連結。對於 Python 3.8 之前的版本,混合模式偵錯在搭配虛擬環境(venvs)時應該能夠正常運作。
在全域環境中運行不會對任何版本的 Python 造成這些問題。
安裝 Python 符號
當你第一次開始在混合模式除錯時,可能會看到一個 「Python 符號需求 」對話框。 你只需要在每個 Python 環境中安裝一次符號。 如果你透過 Visual Studio 安裝程式(Visual Studio 2017 及更新版本)安裝 Python 支援,符號會自動包含在內。 欲了解更多資訊,請參閱 Visual Studio 中安裝 Python 直譯器的除錯符號。
存取 Python 原始碼
你可以在除錯時公開標準 Python 的原始碼。
下載適合你版本的 Python 原始碼壓縮檔,並將程式碼解壓到資料夾中。
當 Visual Studio 提示 Python 原始碼的位置時,指向解壓資料夾中的特定檔案。
在 C/C++ 專案中啟用混合模式除錯
Visual Studio 2017 版本 15.5 及以上版本支援從 C/C++ 專案進行混合模式除錯。 例如,當你想要 將 Python 嵌入到其他應用程式中,如同在 python.org 上所描述的那樣。
以下步驟說明如何啟用 C/C++ 專案的混合模式除錯:
在 解決方案總管中,右鍵點擊 C/C++ 專案,選擇 屬性。
在 屬性頁面 面板中,選擇「 設定屬性>除錯 」標籤。
展開下拉選單,開啟 除錯器 啟動選項,並選擇 Python/原生除錯。
備註
如果你沒看到 Python/原生除錯 選項,你必須先使用 Visual Studio 安裝程式安裝 Python 原生開發工具 。 原生除錯選項可在 Python 開發工作負載中提供。 如需詳細資訊,請參閱 在Visual Studio中安裝 Python 支援。
選取 [確定] 儲存變更。
除錯程式啟動器
使用這種方法時,你無法除錯 py.exe 程式啟動器,因為它會產生子 python.exe 程序。 除錯器不會連接到子程序。 在這種情況下,解決方法是直接啟動 python.exe 程式並使用參數,如下:
在 C/C++ 專案的 屬性頁面 窗格中,前往 設定屬性>除錯 標籤。
對於 Command 選項,請指定程式檔案的完整
python.exe路徑。請在 指令參數 欄位指定你想要的參數。
連接混合模式偵錯器
在 Visual Studio 2017 版本 15.4 及更早版本中,只有在 Visual Studio 啟動 Python 專案時才啟用直接混合模式除錯。 支援有限,因為 C/C++ 專案僅使用原生除錯器。
在這種情況下,解決方法是將除錯器分別連接:
可選擇「 除錯>開始」 或使用鍵盤快捷鍵 Ctrl+F5 開啟 C++ 專案,無需除錯。
若要將混合模式除錯器附加到現有程序,請選擇 除錯>附加至程序。 對話開始了。
在 「附加到流程 」對話框中,從列表中選擇相應的流程。
在「 附加」 欄位,請使用 「選擇 」選項開啟 「選擇程式碼類型 」對話框。
在 「選擇程式碼類型 」對話框中,選擇 「除錯這些程式碼類型 」選項。
在清單中,選擇 Python 的勾選框,然後選擇 確定。
選擇 Attach 以啟動除錯器。
小提示
你可以在 C++ 應用程式中加入暫停或延遲,確保它不會在你附加除錯器前先呼叫你想除錯的 Python 程式碼。
探索混合模式專屬的功能
Visual Studio 提供多項混合模式除錯功能,讓您更容易除錯應用程式:
使用合併呼叫堆疊
呼叫堆疊視窗會顯示原生與 Python 堆疊框交錯顯示,並且標記了兩者之間的過渡。
- 若要讓轉換顯示為 [外部程式碼] ,但未指定轉換方向,請使用 工具>選項 面板。 展開 「所有設定>除錯>一般 」區塊,選擇 「啟用僅我的程式碼 」勾選框。
- 若要讓轉換顯示為 [外部程式碼], 但未指定轉換方向,請使用 工具>選項 對話框。 展開偵錯>一般 區段,選擇啟用僅限我的程式碼的核取方塊,然後選擇確定。
- 要啟用任何通話框,請雙擊該框架。 此動作同時也會開啟對應的原始碼(如可能)。 如果原始碼無法取得,框架仍會保持活躍狀態,並可檢查本地變數。
Python 與原生程式碼之間的步驟
Visual Studio 提供 Step Into (F11) 或 Step Out (Shift+F11) 指令,使混合模式除錯器能正確處理不同程式碼類型的變更。
當 Python 呼叫以 C 語言實作的方法時,介入呼叫該方法的呼叫會停止在實作該方法的原生函式開頭。
當原生程式碼呼叫 Python API 函式,導致 Python 程式碼被調用時,也會發生同樣的行為。 在原本以 Python 定義的函式值上進行
PyObject_CallObject呼叫時,將在 Python 函式的開頭停下。從 Python 直接切換到原生功能也支援透過 Python 透過 ctypes 調用的原生函式。
在原生程式碼中使用 PyObject 值檢視
當原生(C 或 C++)框架處於啟用狀態時,其本地變數會顯示在除錯器的 Locals 視窗中。 在原生 Python 擴充模組中,許多變數是 PyObject 型別(這是 _object 的 typedef),或其他幾種基本的 Python 型別。 在混合模式除錯中,這些值會呈現另一個子節點,標示為 [Python 視圖]。
要查看變數的 Python 表示,請展開節點。 變數的視圖與 Python 框架中若有本地變數參考相同物件時所見相同。 此節點的子節點是可編輯的。
要停用此功能,請在 本地視窗 任意位置右鍵點擊,並切換 Python>顯示 Python View 節點 選單選項:
C 類型顯示 Python 視圖節點
以下 C 類型顯示 [Python 視圖] 節點(若啟用):
PyObjectPyVarObjectPyTypeObjectPyByteArrayObjectPyBytesObjectPyTupleObjectPyListObjectPyDictObjectPySetObjectPyIntObjectPyLongObjectPyFloatObjectPyStringObjectPyUnicodeObject
[Python 視圖] 不會自動顯示在你自己編寫的型別上。 當你為 Python 3.x 撰寫擴充功能時,這種缺失通常不是問題。 任何物件最終都會有一個 ob_base 列出的 C 類型欄位,這會導致 [Python 視圖] 出現。
在 Python 程式碼中查看原生值
當 Python 框架啟動時,你可以在 Locals 視窗中啟用 [C++ 視圖]來顯示原生值。 預設不會啟用這項功能,
要啟用此功能,請在 本地 視窗中右鍵點擊,並設定 Python>顯示 C++ View Nodes 選單選項。
[C++ 視圖] 節點提供 C/C++ 底層結構的表示,就像在原生框架中所看到的一樣。 它會顯示一個實例
_longobject(其中PyLongObject是一個 typedef)的 Python 的長整數,並嘗試推斷由你自己撰寫的原生類別的型別。 此節點的子節點是可編輯的。
如果物件的子欄位是型別 PyObject,或是其他支援型別,則該物件會有一個 [Python 視圖] 表示節點(如果啟用這些表示功能)。 這種行為使得在 Python 中導覽未直接暴露的連結的物件圖變得可能。
與使用 Python 物件元資料來決定物件類型的 [Python 視圖] 節點不同,C ++ 視圖沒有同樣可靠的機制。 一般來說,給定一個 Python 值(也就是 PyObject 參考),無法可靠地判斷背後是哪個 C/C++ 結構。 混合模式除錯器會透過查看物件類型中具有函式指標類型的各種欄位(例如 PyTypeObject 欄位 ob_type 所參考的欄位)來猜測類型。 如果其中一個函數指標參考一個可解析的函數,且該函數的 self 參數型別比 更具體, PyObject*則該函數型別被視為後盾型別。
請考慮以下範例,其中 ob_type->tp_init 給定物件的值指向以下函數:
static int FobObject_init(FobObject* self, PyObject* args, PyObject* kwds) {
return 0;
}
在這種情況下,除錯器可以正確推斷出物件的 C 型態為 FobObject。 如果除錯器無法從 中找出更精確的型別 tp_init,就會轉向其他欄位。 如果無法從這些欄位推導出型別, [C++ 視圖] 節點會將物件呈現為實 PyObject 例。
為了讓自訂類型有用的表現形式,最好在類型註冊時至少指定一個專用函式,並使用強型別的 self 參數。 大多數類型自然符合這個要求。 對於其他類型,使用 tp_init 檢查通常是最方便的方式。 對於一個僅用於啟用除錯器型別推論的型別的虛擬實作 tp_init ,可以立即回傳零,就像前面的例子一樣。
檢視與標準 Python 除錯的差異
混合模式除錯器與 標準的 Python 除錯器不同。 它引入了一些額外功能,但缺少一些與 Python 相關的功能,具體如下:
- 不支援的功能包括條件斷點、 除錯互動 視窗,以及跨平台遠端除錯。
- 立即視窗雖然可用,但功能有限,包含本節列出的所有限制。
- 支援的 Python 版本僅包含 CPython 2.7 與 3.3+。
- 若要使用 Python 搭配 Visual Studio Shell(例如,如果你用內建安裝程式安裝它),Visual Studio 無法開啟 C++ 專案。 因此,C++ 檔案的編輯體驗僅相當於基本的文字編輯器。 然而,C/C++ 除錯與混合模式除錯在 Shell 中完全支援,包含原始碼、進入原生程式碼,並在除錯器視窗中進行 C++ 表達式評估。
- 當你在 Locals 和 Watch 除錯工具視窗中查看 Python 物件時,混合模式除錯器只會顯示物件的結構。 它不會自動評估屬性或顯示計算出的屬性。 對於集合,它只顯示內建集合類型的元素(
tuple, ,listdict,set)。 自訂收藏類型不會以集合形式視覺化,除非它們是從某個內建集合類型繼承而來的。 - 表達式評估的處理方式如下節所述。
使用表達式評估
標準的 Python 除錯器允許除錯進程在程式碼中停止時,在 Watch 和 Immediate 視窗中執行任意 Python 表達式的評估,並且只要未被 I/O 操作或其他類似系統呼叫阻擋。 在混合模式除錯中,任意表達式只能在 Python 程式碼中暫停之後、斷點之後,或是在進入程式碼時進行評估。 表達式只能在發生斷點或步進操作的執行緒上運算。
當除錯器在原生程式碼中停止,或在 Python 程式碼中停止,且所描述的條件不適用,例如在步出操作後或在不同執行緒中時。 表達式評估僅限於存取目前所選框架範圍內的局部與全域變數、存取其欄位,以及用文字索引內建的集合類型。 例如,以下表達式可在任何情境中評估(前提是所有識別符皆指向現有變數與適當類型的欄位):
foo.bar[0].baz['key']
混合模式除錯器也會以不同的方式解析這類表達式。 所有成員存取操作僅查找直接屬於物件中的欄位(例如物件的 __dict__ 或 __slots__ 中的條目,或透過 tp_members 將原生結構體暴露給 Python 的欄位),並忽略任何 __getattr__、__getattribute__ 或描述子邏輯。 同樣地,所有索引操作都會忽略 __getitem__,直接存取集合的內部資料結構。
為了一致性,所有符合限制式求值限制的表達式都採用此命名解析方案。 此方案無論在當前停止點是否允許任意表達式,均可應用。 當有完整功能的評估器可用時,若要強制正確的 Python 語意,請將表達式以括號內標示:
(foo.bar[0].baz['key'])