python实现编译器的编写_编写 Python 的 C++ 扩展 - Visual Studio | Microsoft Docs

创建适用于 Python 的 C++ 扩展Create a C++ extension for Python

11/19/2018

JoshuaPartlow.png?size=32

olprod.png?size=32

本文内容

使用 C++(或 C)编写的模块常用于扩展 Python 解释器的功能和启用对低级别操作系统功能的访问。Modules written in C++ (or C) are commonly used to extend the capabilities of a Python interpreter as well as to enable access to low-level operating system capabilities. 主要有以下 3 种类型的模块:There are three primary types of modules:

加速器模块:Python 是一种解释型语言,某些代码段可使用 C++ 进行编写来提高性能。Accelerator modules: because Python is an interpreted language, certain pieces of code can be written in C++ for higher performance.

包装器模块:向 Python 代码公开现有 C/C++ 接口,或公开易于通过 Python 使用的更“Python 化”的 API。Wrapper modules: expose existing C/C++ interfaces to Python code or expose a more "Pythonic" API that's easy to use from Python.

低级别系统访问模块:用于访问 CPython 运行时的较低级别功能、操作系统或基础硬件。Low-level system access modules: created to access lower-level features of the CPython runtime, the operating system, or the underlying hardware.

本文介绍了如何为 CPython 生成 C++ 扩展模块,该模块计算双曲正切并从 Python 代码中调用它。This article walks through building a C++ extension module for CPython that computes a hyperbolic tangent and calls it from Python code. 首先使用 Python 实现此例程,以演示使用 C++ 实现此相同例程时可获得的相对性能提升。The routine is implemented first in Python to demonstrate the relative performance gain of implementing the same routine in C++.

本文还演示了使 C++ 可用于 Python 的两种方法:This article also demonstrates two ways to make the C++ available to Python:

Python 文档中所述的标准 CPython 扩展The standard CPython extensions as described in the Python documentation

PyBind11,由于其简单易用,推荐用于 C++ 11。PyBind11, which is recommended for C++ 11 because of its simplicity.

本文末尾的替代方法下可找到这些方法与其他方法的比较。A comparison between these and other means is found under alternative approaches at the end of this article.

The completed sample from this walkthrough can be found on python-samples-vs-cpp-extension (GitHub).

先决条件Prerequisites

将 C++ 桌面开发和 Python 开发工作负载作为默认选项安装的 Visual Studio 2017 或更高版本。Visual Studio 2017 or later with both the Desktop Development with C++ and Python Development workloads installed with default options.

在“Python 开发”工作负载中,同时选择右侧的“Python 本机开发工具”方框。In the Python Development workload, also select the box on the right for Python native development tools. 此选项设置本文所述的大部分配置。This option sets up most of the configuration described in this article. (此选项还自动包括 C++ 工作负载。)(This option also includes the C++ workload automatically.)

cpp-install-native.png?view=vs-2019

提示

安装数据科学和分析应用程序工作负载还包括默认安装 Python 和“Python 本机开发工具”选项。Installing the Data science and analytical applications workload also includes Python and the Python native development tools option by default.

有关详细信息,请参阅安装针对 Visual Studio 的 Python 支持,其中包括使用其他版本的 Visual Studio。For more information, see Install Python support for Visual Studio, including using other versions of Visual Studio. 如果单独安装 Python,请务必在安装程序的“高级选项”下选择“下载调试符号”和“下载调试二进制文件”。If you install Python separately, be sure to select Download debugging symbols and Download debug binaries under Advanced Options in the installer. 此选项确保在选择进行调试生成时能够使用必要的调试库。This option ensures that you have the necessary debug libraries available if you choose to do a debug build.

创建 Python 应用程序Create the Python application

要在 Visual Studio 中创建新 Python 项目,请选择“文件” > “新建” > “项目”。Create a new Python project in Visual Studio by selecting File > New > Project. 搜索"Python",选择“Python 应用程序”模板,为其提供合适的名称和位置,然后选择“确定”。Search for "Python", select the Python Application template, give it a suitable name and location, and select OK.

必须具备 32 位 Python 解释器才可使用 C++(推荐 Python 3.6 或更高版本)。Working with C++ requires that you use a 32-bit Python interpreter (Python 3.6 or above recommended). 在 Visual Studio 的“解决方案资源管理器”窗口中,展开项目节点,然后展开“Python 环境”节点。In the Solution Explorer window of Visual Studio, expand the project node, then expand the Python Environments node. 如果发现默认值不是 32 位环境(加粗或标记为“全局默认值”),请按照选择项目的 Python 环境中的说明进行操作。If you don't see a 32-bit environment as the default (either in bold, or labeled with global default), then follow the instructions on Select a Python environment for a project. 如果未安装 32 位解释器,请参阅安装 Python 解释器。If you don't have a 32-bit interpreter installed, see Install Python interpreters.

在项目的 .py 文件中,粘贴以下代码,用于对双曲正切的计算进行基准测试(无需使用数学库即可实现,以便简化比较)。In the project's .py file, paste the following code that benchmarks the computation of a hyperbolic tangent (implemented without using the math library for easier comparison). 可随意手动输入代码,体验某些 Python 编辑功能。Feel free to enter the code manually to experience some of the Python editing features.

from itertools import islice

from random import random

from time import perf_counter

COUNT = 500000 # Change this value depending on the speed of your computer

DATA = list(islice(iter(lambda: (random() - 0.5) * 3.0, None), 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) 运行程序以查看结果。Run the program using Debug > Start without Debugging (Ctrl+F5) to see the results. 用户可调整 COUNT 变量,更改基准检验的运行时长。You can adjust the COUNT variable to change how long the benchmark takes to run. 对于本演练,请将计数设置为让基准检验运行约 2 秒。For the purposes of this walkthrough, set the count so that the benchmark take around two seconds.

提示

运行基准检验时,请始终使用“调试” > “启动而不调试”,从而避免在 Visual Studio 调试器中运行代码时产生开销。When running benchmarks, always use Debug > Start without Debugging to avoid the overhead incurred when running code within the Visual Studio debugger.

创建核心 C++ 项目Create the core C++ projects

按照本节中的说明,创建名为“superfastcode”和“superfastcode2”的两个完全相同的 C++ 项目。Follow the instructions in this section to create two identical C++ projects named "superfastcode" and "superfastcode2". 之后,在每个项目中使用不同的方法将 C++ 代码公开到 Python。Later you'll use different means in each project to expose the C++ code to Python.

确保将 PYTHONHOME 环境变量设置为要使用的 Python 解释器。Make sure the PYTHONHOME environment variable is set to the Python interpreter you want to use. Visual Studio 中的 C++ 项目依赖此变量来定位 python.h 等文件,这些文件在创建 Python 扩展时使用。The C++ projects in Visual Studio rely on this variable to locate files such as python.h, which are used when creating a Python extension.

在“解决方案资源管理器”中右键单击此解决方案并选择“添加” > “新建项目”。Right-click the solution in Solution Explorer and select Add > New Project. 一个 Visual Studio 解决方案可同时包含 Python 和 C++ 项目(这是使用 Visual Studio for Python 的优势之一)。A Visual Studio solution can contain both Python and C++ projects together (which is one of the advantages of using Visual Studio for Python).

搜索“C++”,选择“空项目”,指定名称“superfastcode”(第二个项目则指定名称“superfastcode2”),然后选择“确定”。Search on "C++", select Empty project, specify the name "superfastcode" ("superfastcode2" for the second project), and select OK.

提示

如果在 Visual Studio 中安装了 Python 本机开发工具,则可改为从 Python 扩展模块模板开始,其中有许多现成的以下所述的内容。With the Python native development tools installed in Visual Studio, you can start with the Python Extension Module template instead, which has much of what's described below already in place. 但是,对于本演练,从空项目开始可以逐步演示如何生成扩展模块。For this walkthrough, though, starting with an empty project demonstrates building the extension module step by step. 了解该过程后,在编写自己的扩展时,使用模板可帮助你节省时间。Once you understand the process, the template saves you time when writing your own extensions.

在新项目中创建 C++ 文件,方法是右键单击“源文件”节点,然后选择“添加” > “新建项”,选择“C++ 文件”,将其命名为 module.cpp,然后选择“确定”。Create a C++ file in the new project by right-clicking the Source Files node, then select Add > New Item, select C++ File, name it module.cpp, and select OK.

重要

需要扩展名为 .cpp 的文件才能在随后步骤中打开 C++ 属性页。A file with the .cpp extension is necessary to turn on the C++ property pages in the steps that follow.

右键单击“解决方案资源管理器”中的 C++ 项目,然后选择“属性”。Right-click the C++ project in Solution Explorer, select Properties.

在显示的“属性页”对话框顶部,将“配置”设置为“所有配置”,并将“平台”设置为“Win32”。At the top of the Property Pages dialog that appears, set Configuration to All Configurations and Platform to Win32.

如下表所述设置特定属性,然后选择“确定”。Set the specific properties as described in the following table, then select OK.

TabTab

PropertyProperty

“值”Value

常规General

常规 > 目标名称General > Target Name

指定想要在 from...import 语句中从 Python 引用的模块的名称。Specify the name of the module as you want to refer to it from Python in from...import statements. 定义 Python 的模块时,在 C++ 中使用相同的名称。You use this same name in the C++ when defining the module for Python. 如果想要将项目的名称用作模块名称,请保留默认值 $(ProjectName)。If you want to use the name of the project as the module name, leave the default value of $(ProjectName).

常规 > 目标扩展名General > Target Extension

.pyd.pyd

项目默认值 > 配置类型Project Defaults > Configuration Type

动态库(.dll)Dynamic Library (.dll)

C/C++ > 常规C/C++ > General

附加包含目录Additional Include Directories

根据相应的安装添加 Python“include” 文件夹,例如 c:\Python36\include。Add the Python include folder as appropriate for your installation, for example, c:\Python36\include.

C/C++ > 预处理器C/C++ > Preprocessor

预处理器定义Preprocessor Definitions

仅 CPython:将 Py_LIMITED_API; 添加到字符串开头(包括分号)。CPython only: add Py_LIMITED_API; to the beginning of the string (including the semicolon). 此定义会限制可从 Python 调用的某些函数,并使代码在 Python 不同版本之间更易于移植。This definition restricts some of the functions you can call from Python and makes the code more portable between different versions of Python. 如果使用的是 PyBind11,请勿添加此定义,否则将会看到生成错误。If you're working with PyBind11, don't add this definition, otherwise you'll see build errors.

C/C++ > 代码生成C/C++ > Code Generation

运行时库Runtime Library

多线程 DLL (/MD)(请参阅下面的“警告”)Multi-threaded DLL (/MD) (see Warning below)

链接器 > 常规Linker > General

附加库目录Additional Library Directories

根据相应的安装添加包含 .lib 文件的 Python“libs”文件夹,例如 c:\Python36\libs。Add the Python libs folder containing .lib files as appropriate for your installation, for example, c:\Python36\libs. (务必指向包含 .lib 文件的“libs”文件夹,而非包含 .py 文件的 Lib 文件夹。)(Be sure to point to the libs folder that contains .lib files, and not the Lib folder that contains .py files.)

提示

如果在项目属性中未看到 C/C++ 选项卡,这是因为项目不包含标识为 C/C++ 源文件的任何文件。If you don't see the C/C++ tab in the project properties, it's because the project doesn't contain any files that it identifies as C/C++ source files. 如果创建的源文件不含 .c 或 .cpp 扩展名,则可能出现这种情况。This condition can occur if you create a source file without a .c or .cpp extension. 例如,如果之前在“新建项”对话框中不小心输入了 module.coo(而不是 module.cpp),则 Visual Studio 会创建文件,但不会将文件类型设置为“C/C+ 代码”,而正是它激活 C/C++ 属性选项卡。即使将文件重命名为带 .cpp,此类识别错误仍会存在。For example, if you accidentally entered module.coo instead of module.cpp in the new item dialog earlier, then Visual Studio creates the file but doesn't set the file type to "C/C+ Code," which is what activates the C/C++ properties tab. Such misidentification remains the case even if you rename the file with .cpp. 为了正确设置文件类型,请在“解决方案资源管理器”中右键单击文件,选择“属性”,然后将“文件类型”设置为“C/C++ 代码”。To set the file type properly, right-click the file in Solution Explorer, select Properties, then set File Type to C/C++ Code.

警告

即使对于调试配置,也始终将“C/C++” > “代码生成” > “运行时库”选项设置为“多线程 DLL (/MD)”,因为此设置是生成非调试 Python 二进制文件时使用的设置。Always set the C/C++ > Code Generation > Runtime Library option to Multi-threaded DLL (/MD), even for a debug configuration, because this setting is what the non-debug Python binaries are built with. 使用 CPython 时,如果碰巧设置了“多线程调试 DLL (/MDd)”选项,生成“调试”配置会生成错误 C1189:Py_LIMITED_API is incompatible with Py_DEBUG, Py_TRACE_REFS, and Py_REF_DEBUG。With CPython, if you happen to set the Multi-threaded Debug DLL (/MDd) option, building a Debug configuration produces error C1189: Py_LIMITED_API is incompatible with Py_DEBUG, Py_TRACE_REFS, and Py_REF_DEBUG. 此外,如果删除 Py_LIMITED_API(使用 CPython 时必须这样做,但在使用 PyBind11 时则不是)以免出现生成错误,Python 会在尝试导入模块时发生故障。Furthermore, if you remove Py_LIMITED_API (which is required with CPython, but not PyBind11) to avoid the build error, Python crashes when attempting to import the module. (如下所述,对 PyModule_Create 的 DLL 调用中发生故障,显示输出消息“Python 错误:PyThreadState_Get: 无当前线程”。)(The crash happens within the DLL's call to PyModule_Create as described later, with the output message of Fatal Python error: PyThreadState_Get: no current thread.)

/MDd 选项用于生成 Python 调试二进制文件(例如 python_d.exe),但对扩展 DLL 选择此选项仍会导致 Py_LIMITED_API 的生成错误。The /MDd option is used to build the Python debug binaries (such as python_d.exe), but selecting it for an extension DLL still causes the build error with Py_LIMITED_API.

右键单击 C++ 项目,然后选择“生成”来测试配置(包括“调试”和“发布”)。Right-click the C++ project and select Build to test your configurations (both Debug and Release). .pyd 文件位于“调试”和“发布”下的“解决方案”文件夹中,而非位于 C++ 项目文件夹本身。The .pyd files are located in the solution folder under Debug and Release, not the C++ project folder itself.

将以下代码添加到 C++ 项目的 module.cpp 文件:Add the following code to the C++ project's module.cpp file:

#include

#include

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++ 项目以确认代码正确。Build the C++ project again to confirm that your code is correct.

如果尚未执行此操作,请重复上述步骤,再创建一个内容相同的项目,且名为“superfastcode2”。If you haven't already done so, repeat the steps above to create a second project named "superfastcode2" with identical contents.

将 C++ 项目转换为适用于 Python 的扩展Convert the C++ projects to extensions for Python

若要使 C++ DLL 成为适用于 Python 的扩展,首先应修改导出的方法以与 Python 类型交互。To make the C++ DLL into an extension for Python, you first modify the exported methods to interact with Python types. 然后,添加一个可导出模块的函数以及该模块的方法的定义。You then add a function that exports the module, along with definitions of the module's methods.

以下各节介绍如何使用 CPython 扩展和 PyBind11 执行这些步骤。The sections that follow explain how to perform these steps using both the CPython extensions and PyBind11.

CPython 扩展CPython extensions

要了解本节介绍的有关 Python 3.x 的背景信息,请在 python.org 上参阅 Python/C API Reference Manual(Python/C API 参考手册),并着重了解 Module Objects(模块对象)(请不要忘记从右上角的下拉列表中选择你的 Python 版本,以便查看正确的文档)。For background on what's shown in this section for Python 3.x, refer to the Python/C API Reference Manual and especially Module Objects on python.org (remember to select your version of Python from the drop-down control on the upper right to view the correct documentation).

如果使用的是 Python 2.7,请改为参阅 python.org 上的 Extending Python 2.7 with C or C++(使用 C 或 C++ 扩展 Python 2.7)和 Porting Extension Modules to Python 3(将扩展模块移植到 Python 3)。

在 module.cpp 的顶部,包含 Python.h:At the top of module.cpp, include Python.h:

#include

修改 tanh_impl 方法以接受和返回 Python 类型(即,PyObject*):Modify the tanh_impl method to accept and return Python types (a PyObject*, that is):

PyObject* tanh_impl(PyObject *, PyObject* o) {

double x = PyFloat_AsDouble(o);

double tanh_x = sinh_impl(x) / cosh_impl(x);

return PyFloat_FromDouble(tanh_x);

}

添加一个定义如何向 Python 呈现 C++ tanh_impl 函数的结构:Add a structure that defines how the C++ tanh_impl function is presented to Python:

static PyMethodDef superfastcode_methods[] = {

// The first property is the name exposed to Python, fast_tanh, the second is the C++

// function name that contains the implementation.

{ "fast_tanh", (PyCFunction)tanh_impl, METH_O, nullptr },

// Terminate the array with an object containing nulls.

{ nullptr, nullptr, 0, nullptr }

};

添加一个定义模块的结构,因为要在 Python 代码中引用它(特别是在使用 from...import 语句时)。Add a structure that defines the module as you want to refer to it in your Python code, specifically when using the from...import statement. (使其与“配置属性” > “常规” > “目标名称”中的项目属性中的值匹配。)在以下示例中,“superfastcode”模块名意味着可在 Python 中使用 from superfastcode import fast_tanh,因为 fast_tanh 是在 superfastcode_methods 中定义的。(Make this match the value in the project properties under Configuration Properties > General > Target Name.) In the following example, the "superfastcode" module name means you can use from superfastcode import fast_tanh in Python, because fast_tanh is defined within superfastcode_methods. (C++ 项目内部使用的文件名,如 module.cpp,是无关紧要的。)(Filenames internal to the C++ project, like module.cpp, are inconsequential.)

static PyModuleDef superfastcode_module = {

PyModuleDef_HEAD_INIT,

"superfastcode", // Module name to use with Python import statements

"Provides some functions, but faster", // Module description

0,

superfastcode_methods // Structure that defines the methods of the module

};

添加 Python 加载模块时要调用的方法,该模块必须命名为 PyInit_,其中 与 C++ 项目的“常规” > “目标名称”属性完全匹配(也就是说,其与项目生成的 .pyd 的文件名相匹配)。Add a method that Python calls when it loads the module, which must be named PyInit_, where exactly matches the C++ project's General > Target Name property (that is, it matches the filename of the .pyd built by the project).

PyMODINIT_FUNC PyInit_superfastcode() {

return PyModule_Create(&superfastcode_module);

}

将目标配置设置为“发布”并再次生成 C++ 项目来验证代码。Set the target configuration to Release and build the C++ project again to verify your code. 如果遇到错误,请参阅下面的疑难解答一节。If you encounter errors, see the Troubleshooting section below.

PyBind11PyBind11

如果已完成上一节中的步骤,你肯定已注意到,你使用了大量样本代码来为 C++ 代码创建必要的模块结构。If you completed the steps in the previous section, you certainly noticed that you used lots of boilerplate code to create the necessary module structures for the C++ code. PyBind11 通过 C++ 头文件中的宏简化了进程,这些宏通过更少的代码实现了相同的结果。PyBind11 simplifies the process through macros in a C++ header file that accomplish the same result with much less code. 有关本节所示内容的背景信息,请参阅PyBind11 基础知识。For background on what's shown in this section, see PyBind11 basics (github.com).

使用 pip 安装 PyBind11:pip install pybind11 或 py -m pip install pybind11。Install PyBind11 using pip: pip install pybind11 or py -m pip install pybind11.

在 module.cpp 的顶部,包含 pybind11.h:At the top of module.cpp, include pybind11.h:

#include

在 module.cpp 的底部,使用 PYBIND11_MODULE 宏来定义 C++ 函数的入口点:At the bottom of module.cpp, use the PYBIND11_MODULE macro to define the entrypoint to the C++ function:

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++ 项目来验证代码。Set the target configuration to Release and build the C++ project to verify your code. 如果遇到错误,请参阅下一节有关疑难解答的内容。If you encounter errors, see the next section on troubleshooting.

疑难解答Troubleshooting

C++ 模块可能无法编译的原因如下:The C++ module may fail to compile for the following reasons:

无法找到 Python.h (E1696:无法打开源文件 "Python.h" 和/或 C1083:无法打开 include 文件:"Python.h":没有此类文件或目录):验证项目属性中的“C/C++” > “常规” > “附加 Include 目录”中的路径是否指向 Python 安装的“include”文件夹。Unable to locate Python.h (E1696: cannot open source file "Python.h" and/or C1083: Cannot open include file: "Python.h": No such file or directory): verify that the path in C/C++ > General > Additional Include Directories in the project properties points to your Python installation's include folder.

无法找到 Python 库:验证项目属性中的“链接器” > “常规” > “附加库目录”中的路径是否指向 Python 安装的“libs”文件夹。Unable to locate Python libraries: verify that the path in Linker > General > Additional Library Directories in the project properties points to your Python installation's libs folder.

与目标体系结构相关的链接器错误:更改 C++ 目标的项目体系结构以匹配 Python 安装。Linker errors related to target architecture: change the C++ target's project architecture to match that of your Python installation. 例如,如果你将 C++ 项目的目标定为 x64,但是 Python 安装是 x86,则将 C++ 项目更改为目标 x86。For example, if you're targeting x64 with the C++ project but your Python installation is x86, change the C++ project to target x86.

测试代码和比较结果Test the code and compare the results

将 DLL 结构化为 Python 扩展后,便可从 Python 项目引用它们,导入模块,并使用其方法。Now that you have the DLLs structured as Python extensions, you can refer to them from the Python project, import the modules, and use their methods.

使 DLL 可供 Python 使用Make the DLL available to Python

可通过两种方法使 DLL 可供 Python 使用。There are two ways to make the DLL available to Python.

如果 Python 项目和 C++ 项目在相同的解决方案中,则使用第一种方法。The first method works if the Python project and the C++ project are in the same solution. 转到“解决方案资源管理器”,右键单击 Python 项目中的“引用”节点,然后选择“添加引用”。Go to Solution Explorer, right-click the References node in your Python project, and then select Add Reference. 在随即出现的对话框中,选择“项目”选项卡,同时选择“superfastcode”和“superfastcode2”项目,然后选择“确定”。In the dialog that appears, select the Projects tab, select both the superfastcode and superfastcode2 projects, and then select OK.

cpp-add-reference.png?view=vs-2019

另一种方法,如以下步骤所述,在全局 Python 环境中安装模块,使其也可供其他 Python 项目使用。The alternate method, described in the following steps, installs the module in the global Python environment, making it available to other Python projects as well. (执行此操作通常需要在 Visual Studio 2017 版本 15.5 和更早版本中刷新该环境的 IntelliSense 完成数据库。(Doing so typically requires that you refresh the IntelliSense completion database for that environment in Visual Studio 2017 version 15.5 and earlier. 从环境中删除模块时也必须执行刷新。)Refreshing is also necessary when removing the module from the environment.)

如果使用的是 Visual Studio 2017 或更高版本,请运行 Visual Studio 安装程序,选择“修改”,然后选择“单个组件” > “编译器、生成工具和运行时” > “Visual C++ 2015.3 v140 工具集”。If you're using Visual Studio 2017 or later, run the Visual Studio installer, select Modify, select Individual Components > Compilers, build tools, and runtimes > Visual C++ 2015.3 v140 toolset. 此步骤是必需的,因为 Python(适用于 Windows)本身是使用 Visual Studio 2015(版本 14.0)生成的,并期望通过此处所述的方法生成扩展时能够使用这些工具。This step is necessary because Python (for Windows) is itself built with Visual Studio 2015 (version 14.0) and expects that those tools are available when building an extension through the method described here. (请注意,可能需要安装 32 位版本的 Python,并将 DLL 定向到 Win32 而不是 x64。)(Note that you may need to install a 32-bit version of Python and target the DLL to Win32 and not x64.)

右键单击 C++ 项目,然后选择“添加” > “新建项”,在项目中创建名为 setup.py 的文件。Create a file named setup.py in the C++ project by right-clicking the project and selecting Add > New Item. 然后选择“C++ 文件 (.cpp)”并命名为 setup.py,再选择“确定”(尽管使用了 C++ 文件模板,但使用 .py 扩展命名文件可让 Visual Studio 将其识别为 Python)。Then select C++ File (.cpp), name the file setup.py, and select OK (naming the file with the .py extension makes Visual Studio recognize it as Python despite using the C++ file template). 编辑器中出现该文件时,以适合扩展方法的形式将以下代码粘贴到其中:When the file appears in the editor, paste the following code into it as appropriate to the extension method:

CPython 扩展(superfastcode 项目):CPython extensions (superfastcode project):

from distutils.core import setup, Extension, DEBUG

sfc_module = Extension('superfastcode', sources = ['module.cpp'])

setup(name = 'superfastcode', version = '1.0',

description = 'Python Package with superfastcode C++ extension',

ext_modules = [sfc_module]

)

请参阅 Building C and C++ Extentions(生成 C 和 C++ 扩展)(python.org),获取此脚本相关文档。See Building C and C++ extensions (python.org) for documentation on this script.

PyBind11(superfastcode2 项目):PyBind11 (superfastcode2 project):

import os, sys

from distutils.core import setup, Extension

from distutils import sysconfig

cpp_args = ['-std=c++11', '-stdlib=libc++', '-mmacosx-version-min=10.7']

sfc_module = Extension(

'superfastcode2', sources = ['module.cpp'],

include_dirs=['pybind11/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],

)

setup.py 代码指示 Python 通过命令行使用 Visual Studio 2015 C++ 工具集生成扩展。The setup.py code instructs Python to build the extension using the Visual Studio 2015 C++ toolset when used from the command line. 打开提升的命令提示符,导航到包含 C++ 项目(即,包含 setup.py 的文件夹),然后输入以下命令:Open an elevated command prompt, navigate to the folder containing the C++ project (that is, the folder that contains setup.py), and enter the following command:

pip install .

或:or:

py -m pip install .

从 Python 调用 DLLCall the DLL from Python

如上一节所述,使 DLL 可用于 Python 之后,现在可以从 Python 代码中调用 superfastcode.fast_tanh 和 superfastcode2.fast_tanh2 函数,并将它们的性能与 Python 实现进行比较:After you've made the DLL available to Python as described in the previous section, you can now call the superfastcode.fast_tanh and superfastcode2.fast_tanh2 functions from Python code and compare their performance to the Python implementation:

在 .py 文件中添加以下行,调用从 DLL 中导出的方法并显示其输出:Add the following lines in your .py file to call methods exported from the DLLs and display their outputs:

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)')

运行 Python 程序(“调试” > “启动而不调试”或按 Ctrl+F5),观察到 C++ 例程的运行速度约比 Python 实现快 5 到 20 倍。Run the Python program (Debug > Start without Debugging or Ctrl+F5) and observe that the C++ routines run approximately five to twenty times faster than the Python implementation. 典型输出形式如下:Typical output appears as follows:

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

如果已禁用“启动而不调试”命令,请右键单击“解决方案资源管理器”中的 Python 项目,选择“设为启动项目”。If the Start Without Debugging command is disabled, right-click the Python project in Solution Explorer and select Set as Startup Project.

尝试增加 COUNT 变量,让差异变得更明显。Try increasing the COUNT variable so that the differences are more pronounced. C++ 模块调试版本的运行速度慢于发布版本的运行速度,因为调试版本优化程度较低,并包含各种错误检查。A Debug build of the C++ module also runs slower than a Release build because the Debug build is less optimized and contains various error checks. 请随意在这些配置之间切换,以便比较。Feel free to switch between those configurations for comparison.

备注

在输出中,可以看到尽管 PyBind11 扩展仍然明显快于直接 Python 实现,但其速度不如 CPython 扩展。In the output, you can see that the PyBind11 extension isn't as fast as the CPython extension, though it's still significantly faster than the straight Python implementation. 这种差异是由于 PyBind11 引入了少量的单调用开销,使其 C++ 接口变得更加简单。The difference is due to a small amount of per-call overhead that PyBind11 introduces in order to make its C++ interface dramatically simpler. 实际上,这种单调用差异可以忽略不计:因为测试代码调用扩展函数 500,000 次,所以此处显示的结果大大放大了开销!This per-call difference is actually quite negligible: because the test code calls the extension functions 500,000 times, the results you see here greatly amplify that overhead! 通常,C++ 函数比此处使用的普通 fast_tanh[2] 方法执行的工作更多,在这种情况下,开销并不重要。Typically, a C++ function does much more work than the trivial fast_tanh[2] methods used here, in which case the overhead is unimportant. 但是,如果实现的方法每秒可能会被调用数千次,则使用 CPython 方法可以获得比 PyBind11 更佳的性能。However, if you're implementing methods that might be called thousands of times per second, using the CPython approach can result in better performance than PyBind11.

调试 C++ 代码Debug the C++ code

Visual Studio 支持一起调试 Python 和 C++ 代码。Visual Studio supports debugging Python and C++ code together. 本节演示了使用 superfastcode 项目的整个过程;superfastcode2 项目的步骤与此相同。This section walks through the process using the superfastcode project; the steps are the same for the superfastcode2 project.

在“解决方案资源管理器”中,右键单击 Python 项目,依次选择“属性”、“调试”选项卡,然后选择“调试” > “启用本机代码调试”选项。Right-click the Python project in Solution Explorer, select Properties, select the Debug tab, and then select the Debug > Enable native code debugging option.

提示

启用本机代码调试后,Python 输出窗口可能在程序完成后立即消失,而不出现通常的“按任何键以继续”暂停界面。When you enable native code debugging, the Python output window may disappear immediately when the program has completed without giving you the usual Press any key to continue pause. 若要强制暂停,请在启用本机代码调试后,向“调试”选项卡上的“运行” > “解释器参数”字段添加 -i 选项。To force a pause, add the -i option to the Run > Interpreter Arguments field on the Debug tab when you enable native code debugging. 此参数会在代码完成后将 Python 解释器置于交互模式,此时它等待用户按 Ctrl+Z > Enter 退出。This argument puts the Python interpreter into interactive mode after the code finishes, at which point it waits for you to press Ctrl+Z > Enter to exit. (或者,如果不介意修改 Python 代码,则可在程序结束时添加 import os 和 os.system("pause") 语句。(Alternately, if you don't mind modifying your Python code, you can add import os and os.system("pause") statements at the end of your program. 此代码会复制初始的暂停提示符。)This code duplicates the original pause prompt.)

选择“文件” > “保存”以保存属性更改。Select File > Save to save the property changes.

在 Visual Studio 工具栏中,将生成配置设置为“调试”。Set the build configuration to Debug in the Visual Studio toolbar.

cpp-set-debug.png?view=vs-2019

由于代码在调试器中运行时通常需要更长时间,可能需要将 .py 文件中的 COUNT 变量更改为小五倍的值(例如,从 500000 更改为 100000)。Because code generally takes longer to run in the debugger, you may want to change the COUNT variable in your .py file to a value that's about five times smaller (for example, change it from 500000 to 100000).

在 C++ 代码中,在 tanh_impl 方法内的第一行设置一个断点,然后启动调试器(F5 或“调试” > “开始调试”)。In your C++ code, set a breakpoint on the first line of the tanh_impl method, then start the debugger (F5 or Debug > Start Debugging). 调用该代码时,调试器将会停止。The debugger stops when that code is called. 如果未命中断点,请检查配置是否设置为“调试”以及是否已保存项目(启动调试器时,不会自动执行该操作)。If the breakpoint is not hit, check that the configuration is set to Debug and that you've saved the project (which does not happen automatically when starting the debugger).

cpp-debugging.png?view=vs-2019

此时,可浏览 C++ 代码、检查变量等。At this point you can step through the C++ code, examine variables, and so on. These features are detailed in Debug C++ and Python together.

替代方法Alternative approaches

下表描述了创建 Python 扩展的各种方法。There are a variety of means to create Python extensions as described in the following table. CPython 和 PyBind11 的前两项已在本文中讨论过。The first two entries for CPython and PyBind11 are what has been discussed in this article already.

方法Approach

年份Vintage

代表用户Representative user(s)

优点Pro(s)

缺点Con(s)

适用于 CPython 的 C/C++ 扩展模块C/C++ extension modules for CPython

19911991

标准库Standard Library

编译、可移植性、引用管理。Compilation, portability, reference management. 扎实的 C 知识。High C knowledge.

PyBind11(推荐用于 C++)PyBind11 (Recommended for C++)

20152015

用于创建现有 C++ 代码的 Python 绑定的轻量型纯标头库。Lightweight, header-only library for creating Python bindings of existing C++ code. 依赖项少。Few dependencies. 兼容 PyPy。PyPy compatibility.

较新,不够成熟。Newer, less mature. 需使用大量 C++11 功能。Heavy use of C++11 features. 支持的编译器较少(包含 Visual Studio)。Short list of supported compilers (Visual Studio is included).

Cython(推荐用于 C)Cython (Recommended for C)

20072007

类似于 Python。Python-like. 非常成熟。Highly mature. 高性能。High performance.

编译、新语法、新工具链。Compilation, new syntax, new toolchain.

20022002

几乎可与任何 C++ 编译器一起使用。Works with just about every C++ compiler.

库套件较大且复杂;包含旧编译器的许多解决方法。Large and complex suite of libraries; contains many workarounds for old compilers.

ctypectypes

20032003

无编译,广泛可用性。No compilation, wide availability.

访问和转变 C 结构的过程比较繁琐且容易出错。Accessing and mutating C structures cumbersome and error prone.

SWIGSWIG

19961996

针对多种语言立即生成绑定。Generate bindings for many languages at once.

如果 Python 是唯一目标,则开销过大。Excessive overhead if Python is the only target.

cfficffi

20132013

易于集成,与 PyPy 兼容。Ease of integration, PyPy compatibility.

较新,不够成熟。Newer, less mature.

20172017

类似于使用 C++ 的 cffi。Similar to cffi using C++.

较新,与 VS 2017 有一些兼容性问题。Newer, may have some issues with VS 2017.

请参阅See also

The completed sample from this walkthrough can be found on python-samples-vs-cpp-extension (GitHub).

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值