程式開發常見問答集¶
常見問題¶
是否有具有中斷點和單步執行功能的原始碼級偵錯器?¶
有的。
下面描述了幾個 Python 偵錯器,內建函式 breakpoint() 允許你進入其中任何一個。
pdb 模組是一個簡單但足夠的 Python 控制台模式偵錯器。它是標準 Python 函式庫的一部分,並記錄在函式庫參考手冊中。你也可以參考 pdb 的程式碼作為範例來編寫自己的偵錯器。
IDLE 互動式開發環境,它是標準 Python 發行版的一部分(通常作為 idlelib 提供),包括一個圖形偵錯器。
PythonWin 是一個 Python IDE,它包含一個基於 pdb 的 GUI 偵錯器。 PythonWin 除錯器為斷點著色並具有許多很酷的功能,例如偵錯非 PythonWin 程式。 PythonWin 作為 pywin32 專案的一部分和作為 ActivePython 的一部分發佈。
Eric 是一個基於 PyQt 和 Scintilla 編輯元件所建構的 IDE。
trepan3k 是一個類似 gdb 的偵錯器。
Visual Studio Code 是一個整合了版本控制軟體與偵錯工具的 IDE。
有數個商業化 Python IDE 包含圖形偵錯器。這些包含:
有沒有工具能夠幫忙找 bug 或執行靜態分析?¶
有的。
如何從 Python 腳本建立獨立的二進位檔案?¶
如果你想要的只是一個使用者可以下載並執行而無需先安裝 Python 發行版的獨立程式,則不需要將 Python 編譯為 C 程式碼的能力。有許多工具可以判斷程式所需的模組集,並將這些模組與 Python 二進位檔案綁定在一起以產生單個可執行檔。
一種方法是使用 freeze 工具,它被包含在 Python 原始碼樹中的 Tools/freeze。它將 Python 位元組碼轉換為 C 陣列;使用 C 編譯器,你可以將所有模組嵌入到一個新程式中,然後將其與標準 Python 模組連結。
它的工作原理是遞迴地掃描你的原始碼以查找引入陳述式(兩種形式)並在標準 Python 路徑和原始碼目錄(對於內建模組)中查找模組。然後它將用 Python 編寫的模組的位元組碼轉換為 C 程式碼(陣列初始化器可以使用 marshal 模組轉換為程式碼物件)並建立一個自訂的組態檔案,該檔案僅包含那些在程式中實際使用的內建模組。然後它編譯產生的 C 程式碼並將其與 Python 直譯器的其餘部分連結以形成一個獨立的二進位檔案,其行為與你的腳本完全一樣。
以下套件可以幫助建立 console 和 GUI 可執行檔案:
Nuitka(跨平台)
PyInstaller(跨平台)
PyOxidizer(跨平台)
cx_Freeze(跨平台)
py2app(僅限 macOS)
py2exe(僅限 Windows)
Python 程式碼是否有編碼標準或風格指南?¶
是的。標準函式庫模組所需的編碼風格稱為 PEP 8。
核心語言¶
為什麼當變數有值時,我仍得到錯誤訊息 UnboundLocalError?¶
在先前能正常運作的程式碼中,當透過在函式主體的某處新增賦值陳述式來修改時,得到 UnboundLocalError 可能會令人驚訝。
這段程式碼:
>>> x = 10
>>> def bar():
... print(x)
...
>>> bar()
10
可以執行,但是這段程式碼:
>>> x = 10
>>> def foo():
... print(x)
... x += 1
導致 UnboundLocalError:
>>> foo()
Traceback (most recent call last):
...
UnboundLocalError: cannot access local variable 'x' where it is not associated with a value
這是因為當你在某個作用域中對變數進行賦值時,該變數會成為該作用域的區域變數,並遮蔽外部作用域中任何同名的變數。由於 foo 中的最後一個陳述式為 x 賦予了一個新值,編譯器將其識別為區域變數。因此,當較早的 print(x) 嘗試印出未初始化的區域變數時就會產生錯誤。
在上面的範例中,你可以透過將其聲明為全域變數來存取外部範圍變數:
>>> x = 10
>>> def foobar():
... global x
... print(x)
... x += 1
...
>>> foobar()
10
需要這個顯式宣告是為了提醒你(與類別和實例變數表面上類似的情況不同)你實際上是在修改外部作用域中變數的值:
>>> print(x)
11
你可以使用 nonlocal 關鍵字在巢狀作用域內做類似的事情:
>>> def foo():
... x = 10
... def bar():
... nonlocal x
... print(x)
... x += 1
... bar()
... print(x)
...
>>> foo()
10
11
Python 的區域變數和全域變數有什麼規則?¶
在 Python 中,僅在函式內部被參照的變數是隱式的全域變數。如果一個變數在函式主體內的任何地方被賦值,除非明確宣告為全域變數,否則它會被假定為區域變數。
雖然起初有點令人驚訝,但稍加思考就可以解釋這一點。一方面,要求被賦值的變數使用 global 可以防止意外的副作用。另一方面,如果所有全域參照都需要 global,那麼你將一直使用 global。你必須將對內建函式或被引入模組的組件的每個參照都宣告為全域。這種混亂會破壞 global 宣告在識別副作用方面的用處。
為什麼以不同的值在迴圈中定義的 lambda 都回傳相同的結果?¶
假設你使用 for 迴圈來定義幾個不同的 lambda(甚至是普通函式),例如:
>>> squares = []
>>> for x in range(5):
... squares.append(lambda: x**2)
這會提供一個包含五個計算 x**2 的 lambda 串列。你可能會預期在呼叫它時,它們會分別回傳 0、1、4、9 和 16,然而當你實際嘗試你會發現它們都回傳 16:
>>> squares[2]()
16
>>> squares[4]()
16
發生這種情況是因為 x 不是 lambda 的區域變數,而是在外部作用域中定義的,且是在呼叫 lambda 時才會存取它,並非於定義時就會存取。在迴圈結束時,x 的值為 4,因此所有函式都回傳 4**2,即為 16。你還可以透過更改 x 的值來驗證這一點,並查看 lambda 運算式的結果如何變化:
>>> x = 8
>>> squares[2]()
64
為了避免這種情況,你需要將值保存在 lambda 的區域變數中,這樣它們就不會依賴於全域 x 的值: