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


如何在 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 來處理模組的 import，而那些模組本身也是 DLL。（這是
   第一個未正式記載的關鍵事實。）請改為連結到 "python*NN*.dll"；它通常
   被安裝在 "C:\Windows\System" 中。*NN* 是 Python 版本，例如 "33" 就
   是指 Python 3.3。

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

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

2. 如果你使用 SWIG，則可輕鬆地建立一個 Python 「擴充模組」，該模組將使
   應用程式的資料和 method（方法）可供 Python 使用。SWIG 會為你處理幾
   乎所有的繁瑣細節。結果就是，你會將 C 程式碼連結*到*你的 .exe 檔裡（
   ！）你**不必**建立 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();  // 初始化 Python。
      initmyAppc();  // 初始化（引入）幫助類別。
      PyRun_SimpleString("import myApp");  // 引入 shadow 類別。

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

   問題 1：使用 "FILE *" 引數的所謂「非常高階」的函式，在多編譯器
   (multi-compiler) 的環境中會無法作用，因為每個編譯器對 "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 更新的指南。
