在專案的
.py
檔案中,貼上下列程式碼。 若要體驗一些
Python 編輯功能
,請嘗試手動輸入程式碼。
此程式碼會計算雙曲正切值,而不使用數學程式庫,而且您會使用原生延伸模組加速。
在 C++ 中重寫程式碼之前,請先以純 Python 撰寫程式碼。 如此一來,您就可以更輕鬆地檢查,以確保您的機器碼正確無誤。
from random import random
from time import perf_counter
COUNT = 500000 # Change this value depending on the speed of your computer
DATA = [(random() - 0.5) * 3 for _ in range(COUNT)]
e = 2.7182818284590452353602874713527
def sinh(x):
return (1 - (e ** (-2 * x))) / (2 * (e ** -x))
def cosh(x):
return (1 + (e ** (-2 * x))) / (2 * (e ** -x))
def tanh(x):
tanh_x = sinh(x) / cosh(x)
return tanh_x
def test(fn, name):
start = perf_counter()
result = fn(DATA)
duration = perf_counter() - start
print('{} took {:.3f} seconds\n\n'.format(name, duration))
for d in result:
assert -1 <= d <= 1, " incorrect values"
if __name__ == "__main__":
print('Running benchmarks with COUNT = {}'.format(COUNT))
test(lambda d: [tanh(x) for x in d], '[tanh(x) for x in d] (Python implementation)')
若要檢視結果,請選取> [偵錯啟動但不偵錯] 或選取Ctrl+F5來執行程式。
您可以調整 COUNT 變數,以變更效能評定的執行時間長度。 在此逐步解說中,請設定計數,讓基準測試需要大約兩秒的時間。
當您執行基準測試時,一律使用偵錯開始而不偵> 錯。 這有助於避免您在 Visual Studio 偵錯工具內執行程式碼時所產生的額外負荷。
建立核心 C++ 專案
請遵循本節中的指示來建立兩個相同的 C++ 專案 superfastcode 和 superfastcode2。 稍後,您將在每個專案中使用不同的方法,將 C++ 程式碼公開給 Python。
在方案總管中,以滑鼠右鍵按一下方案,然後選取 [新增>專案]。 Visual Studio 解決方案可以同時包含 Python 和 C++ 專案,這是使用 Visual Studio for Python 的優點之一。
在 C++上搜尋、選取 [空白專案]、針對第一個專案指定 superfastcode 或 superfastcode2 代表第二個專案,然後選取 [ 確定]。
或者,使用 Visual Studio 中安裝的 Python 原生開發工具,您可以從 Python 延伸模組範本開始。 範本已具備此處所述的大部分內容。
不過,在此逐步解說中,從空白專案開始將逐步示範如何建置延伸模組。 瞭解程式之後,您可以使用範本來節省撰寫自己的擴充功能的時間。
若要在新專案中建立 C++ 檔案,請以滑鼠右鍵按一下[來源檔案] 節點,然後選取 [新增>專案]。
選取 [C++ 檔案],將它命名為 module.cpp,然後選取 [ 確定]。
必須有具有 .cpp 副檔名的檔案,才能在後續步驟開啟 C++ 屬性頁面。
在主要工具列上,使用下拉式功能表來選取下列其中一個組態:
針對 64 位 Python 執行時間,請啟用 x64 組態。
針對 32 位 Python 執行時間,請啟用 Win32 組態。
在方案總管中,以滑鼠右鍵按一下 C++ 專案,選取 [屬性],然後執行下列動作:
a. 針對 [ 組態],輸入 [作用中] ([偵錯]) 。
b. 針對 [平臺],輸入 Active (x64) 或 Active (Win32) ,視您在上一個步驟中的選取專案而定。
當您建立自己的專案時,您會想要同時設定 偵錯 和 發行 組態。 在本單元中,您只會設定偵錯組態,並將它設定為使用 CPython 的版本組建。 此組態會停用 C++ 執行時間的一些偵錯功能,包括判斷提示。 使用 CPython 偵錯二進位檔 (python_d.exe) 需要不同的設定。
如下表所述設定屬性:
C/C++>預處理
前置處理器定義
如果存在,請將 _DEBUG 值變更為 NDEBUG ,以符合非偵錯版本的 CPython。 當您使用 python_d.exe時,請將此值保留不變。
C/C++>程式碼產生
執行階段程式庫
多執行緒 DLL (/MD) ,以符合非偵錯版本的 CPython。 當您使用 python_d.exe時,請將此值保留為 多執行緒偵錯 DLL (/MDd) 。
連接>一般
其他程式庫目錄
新增包含.lib檔案的 Python libs資料夾,適用于您的安裝 (,例如c:\Python36\libs) 。 請務必指向包含.lib檔案的libs資料夾,而不是包含.py檔案的Lib資料夾。
如果專案屬性中未顯示 C/C++ 索引標籤,專案就不會包含識別為 C/C++ 原始程式檔的檔案。 如果您建立不含 .c 或 .cpp 副檔名的來源檔案,就會發生此狀況。
例如,如果您不小心在新的專案對話方塊中輸入 module.coo 而不是 module.cpp ,Visual Studio 會建立檔案,但不會將檔案類型設定為 C/C+ Code,這會啟動 C/C++ 屬性索引標籤。即使您將 副檔名為 .cpp 的檔案重新命名,仍會保留這種錯誤。
若要正確設定檔案類型,請在[方案總管] 中,以滑鼠右鍵按一下檔案,然後選取 [屬性]。 然後,針對 [檔案類型],選取 [C/C++ 程式碼]。
選取 [確定]。
若要測試組態 (偵 錯和 發行) ,請以滑鼠右鍵按一下 C++ 專案,然後選取 [ 建置]。
您會在方案資料夾的 [偵錯] 和 [發行] 底下,找到.pyd檔案,而不是在 C++ 專案資料夾本身。
在 C++ 專案的 module.cpp 檔案中,新增下列程式碼:
#include <Windows.h>
#include <cmath>
const double e = 2.7182818284590452353602874713527;
double sinh_impl(double x) {
return (1 - pow(e, (-2 * x))) / (2 * pow(e, -x));
double cosh_impl(double x) {
return (1 + pow(e, (-2 * x))) / (2 * pow(e, -x));
double tanh_impl(double x) {
return sinh_impl(x) / cosh_impl(x);
重新建置 C++ 專案,確認您的程式碼正確。
如果您尚未這麼做,請重複上述步驟,以建立名為 superfastcode2 且具有相同設定的第二個專案。
將 C++ 專案轉換成適用於 Python 的延伸模組
若要讓 C++ DLL 成為 Python 的延伸模組,請先修改匯出的方法以與 Python 類型互動。 然後,新增匯出模組的函式,以及模組方法的定義。
下列各節說明如何使用 CPython 擴充功能和 PyBind11 來執行這些步驟。
使用 CPython 延伸模組
如需本節所示程式碼的詳細資訊,請參閱 Python/C API 參考手冊 ,特別是 模組物件 頁面。 請務必在右上方的下拉式清單中選取 Python 版本。
在 module.cpp 檔案頂端,包含 Python.h:
#include <Python.h>
tanh_impl修改 方法,以接受並傳回 python 類型 (, PyObject* 也就是) :
PyObject* tanh_impl(PyObject* /* unused module reference */, PyObject* o) {
double x = PyFloat_AsDouble(o);
double tanh_x = sinh_impl(x) / cosh_impl(x);
return PyFloat_FromDouble(tanh_x);
新增結構,定義 C++ tanh_impl 函式如何向 Python 呈現:
static PyMethodDef superfastcode_methods[] = {
// The first property is the name exposed to Python, fast_tanh
// The second is the C++ function with the implementation
// METH_O means it takes a single PyObject argument
{ "fast_tanh", (PyCFunction)tanh_impl, METH_O, nullptr },
// Terminate the array with an object containing nulls.
{ nullptr, nullptr, 0, nullptr }
新增結構,其定義模組以在 Python 程式碼中參考該模組,特別是當您使用 from...import 語句時。
在此程式碼中匯入的名稱應該符合 [組態屬性>一般>目標名稱] 底下專案屬性中的值。
在下列範例中 "superfastcode" ,模組名稱表示您可以在 Python 中使用 from superfastcode import fast_tanh ,因為 fast_tanh 是在 內 superfastcode_methods 定義。 C++ 專案內部的檔案名,例如 module.cpp為 inconsequential。
static PyModuleDef superfastcode_module = {
PyModuleDef_HEAD_INIT,
"superfastcode", // Module name to use with Python import statements
"Provides some functions, but faster", // Module description
superfastcode_methods // Structure that defines the methods of the module
新增 Python 載入模組時所呼叫的方法,該模組名稱必須命名 PyInit_<module-name> 為 ,其中< module-name >完全符合 C++ 專案的一般>目標名稱屬性。 也就是說,它會比對專案所建置之 .pyd 檔案的檔案名。
PyMODINIT_FUNC PyInit_superfastcode() {
return PyModule_Create(&superfastcode_module);
重新建置 C++ 專案,以驗證您的程式碼。 If you come across errors, see the "Troubleshooting" section.
使用 PyBind11
如果您已完成上一節中的步驟,您可能會注意到您已使用許多未定案程式碼來建立 C++ 程式碼的必要模組結構。 PyBind11 可透過 C++ 標頭檔中的宏簡化程式,以完成相同的結果,但程式碼較少。
如需本節中程式碼的詳細資訊,請參閱 PyBind11 基本概念。
使用 pip: pip install pybind11 或 py -m pip install pybind11 安裝 PyBind11。
或者,您可以使用 [Python 環境] 視窗來安裝 PyBind11,然後在 PowerShell 命令中使用其 [在 PowerShell 中開啟 ] 命令來進行下一個步驟。
在相同的終端機中,執行 python -m pybind11 --includes 或 py -m pybind11 --includes 。
此動作會列印您應該新增至專案的C/C++>一般>其他 Include 目錄屬性的路徑清單。 如果前置詞存在,請務必移除 -I 前置詞。
在未包含上一節任何變更的 fresh module.cpp 頂端,包含 pybind11.h:
#include <pybind11/pybind11.h>
在 module.cpp底部,使用 PYBIND11_MODULE 宏來定義 C++ 函式的進入點:
namespace py = pybind11;
PYBIND11_MODULE(superfastcode2, m) {
m.def("fast_tanh2", &tanh_impl, R"pbdoc(
Compute a hyperbolic tangent of a single argument expressed in radians.
)pbdoc");
#ifdef VERSION_INFO
m.attr("__version__") = VERSION_INFO;
#else
m.attr("__version__") = "dev";
#endif
建置 C++ 專案來驗證您的程式碼。 如果您遇到錯誤,請參閱解決方案的下一節「針對編譯失敗進行疑難排解」。
針對編譯失敗進行疑難排解
C++ 模組可能會因為下列原因而無法編譯:
錯誤:找不到Python.h (E1696:無法開放原始碼檔案 「Python.h」和/或C1083:無法開啟 include 檔案:「Python.h」:沒有這類檔案或目錄)
解決方案:確認專案屬性中的路徑 C/C++>一般>其他 Include 目錄指向 Python 安裝的include資料夾。 請參閱建立 Core C++ 專案下的步驟 6。
錯誤:找不到 Python 程式庫
解決方案:確認路徑:專案屬性中的連結器>一般>其他程式庫目錄指向 Python 安裝的libs資料夾。 請參閱建立 Core C++ 專案下的步驟 6。
與目標架構相關的連結器錯誤
解決方案:變更 C++ 目標的專案架構,以符合 Python 安裝的專案架構。 例如,如果您使用 C++ 專案將 Win32 設為目標,但 Python 安裝是 64 位,請將 C++ 專案變更為 x64。
測試程式碼,並比較結果
現在您已將 DLL 結構化成為 Python 延伸模組,您可以從 Python 專案參考它們、匯入模組,並使用其方法。
讓 Python 使用 DLL
您可以透過數種方式讓 Python 使用 DLL。 以下是需要考慮的兩種方法:
如果 Python 專案和 C++ 專案位於相同的方案中,則此第一個方法可運作。 執行下列動作:
在方案總管中,以滑鼠右鍵按一下 Python 專案中的 [參考] 節點,然後選取 [新增參考]。
在出現的對話方塊中,依序選取 [專案] 索引標籤、[superfastcode] 和 [superfastcode2] 專案,然後按一下 [確定]。
替代方法會在 Python 環境中安裝模組,讓模組也可供其他 Python 專案使用。 如需詳細資訊,請參閱setuptools專案檔案。 執行下列動作:
以滑鼠右鍵按一下 C++ 專案、建立名為 setup.py 的檔案,然後選取 [新增]>[新增項目]。
選取 [C++ 檔案] (.cpp) ,將檔案命名 為 setup.py,然後選取 [ 確定]。
使用 .py 副檔名命名檔案可讓 Visual Studio 將其辨識為 Python 檔案,即使使用 C++ 檔案範本也一樣。
當檔案出現在編輯器中時,請視擴充方法適當地將下列程式碼貼到其中:
如需 CPython (superfastcode 專案) 的延伸模組 :
from setuptools import setup, Extension
sfc_module = Extension('superfastcode', sources = ['module.cpp'])
setup(
name='superfastcode',
version='1.0',
description='Python Package with superfastcode C++ extension',
ext_modules=[sfc_module]
針對 PyBind11 (superfastcode2 專案) :
from setuptools import setup, Extension
import pybind11
cpp_args = ['-std=c++11', '-stdlib=libc++', '-mmacosx-version-min=10.7']
sfc_module = Extension(
'superfastcode2',
sources=['module.cpp'],
include_dirs=[pybind11.get_include()],
language='c++',
extra_compile_args=cpp_args,
setup(
name='superfastcode2',
version='1.0',
description='Python package with superfastcode2 C++ extension (PyBind11)',
ext_modules=[sfc_module],
在 C++ 專案中建立名為 pyproject.toml 的第二個檔案,並將下列程式碼貼到其中:
[build-system]
requires = ["setuptools", "wheel", "pybind11"]
build-backend = "setuptools.build_meta"
若要建置延伸模組,請以滑鼠右鍵按一下開啟 的 pyproject.toml 索引標籤,然後選取 [ 複製完整路徑]。 您必須先從路徑中刪除 pyproject.toml 名稱,再使用它。
在方案總管中,以滑鼠右鍵按一下作用中的 Python 環境,然後選取 [管理 Python 套件]。
如果您已安裝套件,您會看到它列于此處。 繼續之前,請按一下 X 將其卸載。
在搜尋方塊中,貼上複製的路徑,從結尾刪除 pyproject.toml ,然後選取 Enter 從該目錄安裝模組。
如果安裝因為許可權錯誤而失敗,請將 --user 新增至結尾,然後再試一次命令。
從 Python 呼叫 DLL
將 DLL 提供給 Python 之後,如上一節所述,您可以從 Python 程式碼呼叫 superfastcode.fast_tanh 和 superfastcode2.fast_tanh2 函式,並將其效能與 Python 實作進行比較。 若要呼叫 DLL,請執行下列動作:
在 .py 檔案中新增下列幾行,以呼叫從 DLL 匯出並顯示其輸出的方法:
from superfastcode import fast_tanh
test(lambda d: [fast_tanh(x) for x in d], '[fast_tanh(x) for x in d] (CPython C++ extension)')
from superfastcode2 import fast_tanh2
test(lambda d: [fast_tanh2(x) for x in d], '[fast_tanh2(x) for x in d] (PyBind11 C++ extension)')
選取> [偵錯開始但不偵錯] 或選取 Ctrl+F5,以執行 Python 程式。
如果停用 [啟動但不偵錯] 命令,請在[方案總管] 中,以滑鼠右鍵按一下 Python 專案,然後選取 [設定為啟始專案]。
觀察 C++ 常式的執行速度大約比 Python 實作快五到 20 倍。 一般輸出會以下列形式呈現:
Running benchmarks with COUNT = 500000
[tanh(x) for x in d] (Python implementation) took 0.758 seconds
[fast_tanh(x) for x in d] (CPython C++ extension) took 0.076 seconds
[fast_tanh2(x) for x in d] (PyBind11 C++ extension) took 0.204 seconds
您可以嘗試增加 COUNT 變數,讓差異更加明顯。
C++ 模組的 偵 錯組建也會執行速度比 發行 組建慢,因為偵錯組建的優化程度較低,而且包含各種錯誤檢查。 請隨意在這些組態之間進行切換以進行比較,但請記得返回並更新您稍早針對發行組態設定設定的屬性。
在輸出中,您可能會看到 PyBind11 延伸模組的速度不如 CPython 延伸模組快,但速度應該比純 Python 實作快。 此差異主要是因為您使用 METH_O 的呼叫不支援多個參數、參數名稱或關鍵字引數。 PyBind11 會產生稍微複雜的程式碼,以提供更類似 Python 的介面給呼叫端。 但是,由於測試程式碼會呼叫函式 500,000 次,因此結果可能會大幅放大該額外負荷!
您可以將迴圈移至 for 機器碼,以進一步降低額外負荷。 此方法牽涉到使用 反覆運算器通訊協定 (或 PyBind11 py::iterable 類型,讓 函式參數) 處理每個元素。 移除 Python 與 C++ 之間的重複轉換是減少處理順序所需時間的有效方式。
針對匯入錯誤進行疑難排解
如果您在嘗試匯入模組時收到 ImportError 訊息,您可以使用下列其中一種方式加以解析:
當您透過專案參考建置時,請確定您的 C++ 專案屬性符合針對 Python 專案啟動的 Python 環境,特別是 Include 和 Library 目錄。
確定您的輸出檔案名為 superfastcode.pyd。 任何其他名稱或延伸模組都會防止匯入。
如果您使用 setup.py 檔案安裝模組,請檢查以確定您已在針對 Python 專案啟動的 Python 環境中執行 pip 命令。 展開 方案總管 中的 Python 環境應該會顯示superfastcode的專案。
偵錯 C++ 程式碼
Visual Studio 可支援同時偵錯 Python 和 C++ 程式碼。 在本節中,您會使用 superfastcode 專案逐步解說程式。 superfastcode2專案的程式相同。
在方案總管中,以滑鼠右鍵按一下 Python 專案,選取 [屬性],選取 [偵錯] 索引標籤,然後選取 [>偵錯啟用機器碼偵錯] 選項。
當您啟用機器碼偵錯時,Python 輸出視窗可能會在程式完成之後立即關閉,而不需提供您平常 的 [按任何] 鍵以繼續 暫停。
解決方案:若要在啟用機器碼偵錯之後強制暫停,請將選項新增 -i 至 [偵錯] 索引標籤上的 [執行>解譯器引數] 欄位。此引數會在程式碼執行之後將 Python 解譯器放入互動式模式,此時它會等候您選取 Ctrl+Z,然後 Enter 關閉視窗。
或者,如果您不考慮修改 Python 程式碼,您可以在程式結尾新增 import os 和 os.system("pause") 語句。 此程式碼會複製原始暫停提示。
選取[檔案>儲存] 以儲存屬性變更。
在 Visual Studio 工具列上,將組建組態設為 [ 偵錯]。
因為程式碼通常需要較長的時間才能在偵錯工具中執行,所以您可能會想要將.py檔案中的變數變更 COUNT 為大約五倍小於預設值的值。 例如,將它從500000變更為100000。
在您的 C++ 程式碼中,于方法的第一行 tanh_impl 設定中斷點,然後選取F5或> [偵錯開始偵錯] 來啟動偵錯工具。
呼叫中斷點程式碼時,偵錯工具會停止。 如果未叫用中斷點,請檢查以確定設定已設定為 [ 偵 錯],且您已儲存專案,這不會在您啟動偵錯工具時自動發生。
在中斷點上,您可以逐步執行 C++ 程式碼、檢查變數等等。 如需這些功能的詳細資訊,請參閱 一起對 Python 和 C++ 進行偵錯。
您可以透過各種方式建立 Python 延伸模組,如下表所述。 本文將討論前兩個數據列 CPython 和 PyBind11 。
代表性使用者