# 在 Windows 使用 Python 的常見問答集

目錄

- 在 Windows 使用 Python 的常見問答集

  - 如何在 Windows 作業系統裡運行 Python 程式？

  - 如何使 Python 腳本可以執行？

  - 為什麼 Python 有時需要這麼長的時間才能開始？

  - 如何從 Python 腳本製作可執行檔？

  - `*.pyd` 檔是否與 DLL 相同？

  - 如何將 Python 嵌入 Windows 應用程式中？

  - 如何防止編輯器在我的 Python 原始碼中插入 tab？

  - 如何在不阻塞的情況下檢查 keypress？

  - 如何解決遺漏 api-ms-win-crt-runtime-l1-1-0.dll 的錯誤？

## 如何在 Windows 作業系統裡運行 Python 程式？

這個問題的答案可能有點複雜。如果你經常使用「命令提示字元」執行程式，那這對你來說不會是什麼難事。如果不然，那就需要更仔細的說明了。

除非你使用某種整合開發環境，否則你最終將會在所謂的「命令提示字元視窗」中 打字輸入 Windows 命令。通常，你可以透過從搜尋欄中搜尋 `cmd` 來建立這樣的視窗。你應該能夠認出何時已啟動這樣的視窗，因為你將看到 Windows「命令提示字元」，它通常看起來像這樣：

    C:\>

第一個字母可能不一樣，且後面也可能還有其他內容，因此你可能會很容易看到類似以下的文字：

    D:\YourName\Projects\Python>

取決於你的電腦如何被設置，以及你最近對它所做的其他操作。一旦你啟動了這樣一個視窗，你就即將可以運行 Python 程式了。

你需要了解，你的 Python 腳本必須被另一個稱為 Python 直譯器的程序來處理。直譯器會讀取你的腳本，將其編譯為位元組碼，然後執行該位元組碼以運行你的程式。那麼，你要如何安排直譯器來處理你的 Python 呢？

首先，你需要確保你的命令視窗會將單字 "py" 識別為啟動直譯器的指令。如果你已經開啟一個命令視窗，則你應該試試輸入命令 `py` 並按下 return 鍵：

    C:\Users\YourName> py

然後，你應該看到類似下面的內容：

    Python 3.6.4 (v3.6.4:d48eceb, Dec 19 2017, 06:04:45) [MSC v.1900 32 bit (Intel)] on win32
    Type "help", "copyright", "credits" or "license" for more information.
    >>>

你已經啟動直譯器中的「互動模式」。這表示你能夠以互動方式輸入 Python 陳述式或運算式，並在等待時執行或計算它們。這是 Python 最強大的功能之一。輸入你所選的幾個運算式並查看結果，可以檢驗此功能：

    >>> print("Hello")
    Hello
    >>> "Hello" * 3
    'HelloHelloHello'

許多人將互動模式作為方便但可高度程式化的計算機。如果你要結束互動式 Python 對話，請呼叫 `exit()` 函式或是按住 `Ctrl` 鍵再輸入 `Z`，然後按下 "`Enter`" 鍵以返回 Windows 命令提示字元。

你可能還會發現你有一個開始功能表項目，像是：開始 ‣ 所有程式 ‣ Python 3.x ‣ Python（命令行），它會讓你在一個新視窗中看到 `>>>` 提示字元。如果是這樣，該視窗將在你呼叫 `exit()` 函式或輸入 ``Ctrl`-`Z`` 字元後消失；Windows 正在該視窗中運行單一個「python」命令，並在你終止直譯器時將其關閉。

現在我們知道 `py` 命令已被識別，而你可以將你的 Python 腳本提供給它。你必須為 Python 腳本給定絕對路徑或相對路徑。假設你的 Python 腳本位於桌面上，並被命名為 `hello.py`，且你的命令提示字元在你的家目錄 (home directory) 中順利地被開啟，那麼你就會看到類似以下的內容：

    C:\Users\YourName>

因此，現在你將透過鍵入 `py` 加上腳本路徑，來使用 `py` 命令將你的腳本提供給 Python：

    C:\Users\YourName> py Desktop\hello.py
    hello

## 如何使 Python 腳本可以執行？

在 Windows 上，標準的 Python 安裝程式已將 .py 副檔名與一種檔案類型 (Python.File) 進行關聯，並為該檔案類型提供一個開啟命令來運行直譯器 (`D:\Program Files\Python\python.exe "%1" %*`)。這足以使腳本能以類似 'foo.py' 的形式從命令提示字元被執行。如果你希望能夠簡單地輸入 'foo' 來執行腳本，而不用加上副檔名，則需要將 .py 新增至 PATHEXT 環境變數中。

## 為什麼 Python 有時需要這麼長的時間才能開始？

通常 Python 在 Windows 上啟動得非常快，但偶爾會有一些錯誤報告，內容是 Python 突然開始需要很長的時間才能啟動。這種情形更令人費解，因為 Python 在其他 Windows 系統上可以正常工作，而那些系統似乎也有相同的配置。

這個問題可能是由發生此問題的電腦上的病毒檢查軟體配置錯誤所引起的。目前已知某些病毒掃描程式，在它們被配置為監視來自檔案系統的所有讀取時，會引入兩個數量級的啟動負擔。請試著檢查您系統上的病毒掃描軟體配置，以確保它們的配置確實相同。當 McAfee 被配置為掃描所有檔案系統的讀取活動時，它是一個特定的違規者。

## 如何從 Python 腳本製作可執行檔？

請參閱如何由 Python 脚本创建能独立运行的二进制程序？該章節列出了用於製作可執行檔的工具清單。

## `*.pyd` 檔是否與 DLL 相同？

是的，.pyd 檔類似於 dll，但也有一些區別。如果你有一個名為 `foo.pyd` 的 DLL，則它必須具有函式 `PyInit_foo()`。接著你可以將 "import foo" 寫入 Python 腳本，Python 將會搜尋 foo.pyd（以及 foo.py、foo.pyc），如果 Python 找到它，將會嘗試呼叫 `PyInit_foo()` 來將它初始化。你並不會將你的 .exe 與 foo.lib 連結 (link)，因為這會導致 Windows 要求 DLL 的存在。

請注意，foo.pyd 的搜尋路徑是 PYTHONPATH，與 Windows 用於搜尋 foo.dll 的路徑不同。此外，foo.pyd 不需存在即可運行你的程式，然而如果你將程式連結了一個 dll，則該 dll 會是必要的。當然，如果你想要 `import foo`，foo.pyd 就是必要的。在 DLL 中，連結是以 `__declspec(dllexport)` 在原始碼中被宣告。在 .pyd 中，連結是在一個可用函式的 list（串列）中被定義。

## 如何將 Python 嵌入 Windows 應用程式中？

在 Windows 應用程式中嵌入 Python 直譯器的過程可以總結如下：

1. 请 不要 直接将 Python 编译到你的 .exe 文件中。 在 Windows 上，Python 必须是一个 DLL 以便处理导入本身就是 DLL 的模块。 （这是首先要知道的未写入文档的关键事实。） 正确的做法，应该是链接到 `pythonNN.dll`；它通常安装在 `C:\Windows\System` 中。 NN 是 Python 的版本号，例如数字 "33" 代表 Python 3.3。

   你可以透過兩種不同的方式連結到 Python。載入時連結 (load-time linking) 表示要連結到 `pythonNN.lib`，而運行時連結 (run-time linking) 表示要連結到 `pythonNN.dll`。（一般註解：`pythonNN.lib` 是 `pythonNN.dll` 相對應的所謂 "import lib"。它只會為鏈接器定義符號。）

   運行時連結大大簡化了連結選項；所有事情都會發生在運行時間。你的程式碼必須使用 Windows `LoadLibraryEx()` 常式 (routine) 來載入 `pythonNN.dll`。該程式碼也必須用 Windows `GetProcAddress()` 常式所取得的指標，來使用 `pythonNN.dll` 中的（即為 Python C API 的）存取常式和資料。對於任何呼叫 Python C API 常式的 C 程式碼，巨集可以讓使用這些指標的過程透明化。

2. 如果你是使用 SWIG，那么很容易创建一个将使得应用的数据和方法可供 Python 使用的 "扩展模块"。 SWIG 将为你处理所有繁琐的细节。 结果是让你链接 置入 你的 .exe 文件当中的 C 代码 (!) 你 无需 创建一个 DLL 文件，而这也简化了链接过程。

3. SWIG 將建立一個 init 函式（一個 C 函式），其名稱取決於擴充模組的名稱。例如，如果模組的名稱是 leo，則該 init 函式會命名為 initleo()。如果你使用 SWIG shadow class（類別），則 init 函式會命名為 initleoc()。這會初始化被 shadow class 所用的大多數隱藏的 helper class。

   你可以將步驟 2 中的 C 程式碼連結到 .exe 檔中的原因是，呼叫初始化函式就等效於 import 模組進 Python！（這是第二個未正式記載的關鍵事實。）

4. 簡而言之，你可以使用以下程式碼，以你的擴充模組初始化 Python 直譯器。

       #include "python.h"
       ...
       Py_Initialize();  // Initialize Python.
       initmyAppc();  // Initialize (import) the helper class.
       PyRun_SimpleString("import myApp");  // Import the shadow class.

5. Python 的 C API 有兩個問題，如果你使用 MSVC（用於建置 pythonNN.dll 的編譯器）以外的編譯器，這些問題將會變得明顯。

   问题 1: 接受 `FILE *` 参数的所谓的 "极高层级" 函数在多编译器环境中将不起作用，因为每个编译器中 `struct FILE` 的概念都会是不同的。 从实现的角度看来这些都是极低层级的函数。

   問題 2：SWIG 在為 void 函式產生包裝函式 (wrapper) 時會產生以下程式碼：

       Py_INCREF(Py_None);
       _resultobj = Py_None;
       return _resultobj;

   唉，Py_None 是一個巨集，它會延伸到一個參照，指向 pythonNN.dll 內部的一種稱為 _Py_NoneStruct 的複雜資料結構。同樣的，此程式碼在多編譯器環境中將會失效。請將此類程式碼替換為：

       return Py_BuildValue("");

   有可能可以使用 SWIG 的 `%typemap` 命令以自動進行程式碼變更，雖然我未曾這樣正常運作過（我是一個完全的 SWIG 新手）。

6. 使用 Python shell 腳本從你的 Windows 應用程式內部建造一個 Python 直譯器視窗不是一個好主意；該視窗將會獨立於你的應用程式視窗系統。與其如此，你（或 wxPythonWindow class）應該要建立一個「本機」直譯器視窗。將該視窗連接到 Python 直譯器是很容易的。你可以將 Python 的 i/o 重定向 (redirect) 到可支援讀取和寫入的任何物件，因此你只需要一個包含 read() 和 write() method 的 Python 物件（在你的擴充模組中被定義）就可以了。

## 如何防止編輯器在我的 Python 原始碼中插入 tab？

FAQ 不建議使用 tab，且 Python 風格指南 PEP 8 建議在分散式 Python 程式碼使用 4 個空格；這也是 Emacs 的 python 模式預設值。

在任何編輯器下，將 tab 和空格混合都是一個壞主意。MSVC 在這方面也是一樣，且可以輕鬆配置為使用空格：選擇工具 ‣ 選項 ‣ Tabs，然後對於「預設」檔案類型，將「Tab 大小」和「縮排大小」設定為 4，然後選擇「插入空格」單選鈕。

如果混合 tab 和空格造成前導空白字元出現問題，則 Python 會引發 `IndentationError` 或 `TabError`。你也可以運行 `tabnanny` 模組，在批次模式下檢查目錄樹。

## 如何在不阻塞的情況下檢查 keypress？

使用 `msvcrt` 模組。這是一個標準的 Windows 專用擴充模組。它定義了一個函式 `kbhit()`，該函式會檢查是否出現鍵盤打擊 (keyboard hit)，以及函式 `getch()`，該函式會取得一個字元且不會將其印出。

## 如何解決遺漏 api-ms-win-crt-runtime-l1-1-0.dll 的錯誤？

使用 Windows 8.1 或更早版本時，若尚未安裝所有的更新，則可能會在 Python 3.5 以上的版本發生這種情況。首先要確保你的作業系統仍受支援並且是最新的，如果這無法解決問題，請造訪 Microsoft 支援頁面以取得關於手動安裝 C Runtime 更新的指南。