在 Visual Studio 中为 Python 创建C++扩展

在本文中,你将为 CPython 生成一个C++扩展模块,以计算双曲正切值并从 Python 代码调用它。 该例程首先在 Python 中实现,以演示在 C++ 中实现相同例程的相对性能提升。

以 C++(或 C) 编写的代码模块通常用于扩展 Python 解释器的功能。 有三种主要类型的扩展模块:

  • 加速器模块:启用加速性能。 由于 Python 是一种解释语言,因此可以在 C++ 中编写加速器模块以提高性能。
  • 包装器模块:向 Python 代码公开现有的 C/C++ 接口,或公开更符合 Python 风格的 API,使其更易于 Python 使用。
  • 低级别系统访问模块:创建系统访问模块以达到运行时、作系统或基础硬件的 CPython 较低级别功能。

本文演示了两种使 C++ 扩展模块可供 Python 使用的方法:

  • 如 CPython所述,请使用标准扩展。
  • 使用 PyBind11,建议使用 C++11,因为其简单性。 若要确保兼容性,请确保使用最新版本的 Python 之一。

本演练的完整示例在 GitHub 上提供了 python-samples-vs-cpp-extension。

先决条件

  • 安装了 Python 开发工作负载的 Visual Studio 2017 或更高版本。 该工作负载包括 Python 本机开发工具,这些工具增加了 C++ 工作负载,并提供构建本机扩展功能所需的工具。

    Python 开发选项列表的屏幕截图,其中突出显示了 Python 本机开发工具选项。

    有关安装选项的详细信息,请参阅 安装对 Visual Studio 的 Python 支持。

    注释

    安装 数据科学和分析应用程序 工作负荷时,默认情况下会安装 Python 和 Python 本机开发工具 选项。

  • 如果单独安装 Python,请确保在 Python 安装程序中的“高级选项”下选择“下载调试符号”。 要在 Python 代码和本机代码之间进行混合模式调试,需要启用此选项。

创建 Python 应用程序

按照以下步骤创建 Python 应用程序。

  1. 通过选择“文件>新建>项目”在 Visual Studio 中创建新的 Python项目。

  2. 在“ 创建新项目 ”对话框中,搜索 python。 选择 Python 应用程序 模板,然后选择“ 下一步”。

  3. 输入项目名称和位置,然后选择“创建”。

    Visual Studio 将创建新项目。 项目在 解决方案资源管理器 中打开,项目文件(.py)将在代码编辑器中打开。

  4. 在 .py 文件中,粘贴以下代码。 若要体验某些 Python 编辑功能,请尝试手动输入代码。

    这段代码在不使用数学库的情况下计算双曲正切,之后将通过 Python 原生扩展对其进行加速。

    小窍门

    在C++中重写代码之前,请在纯 Python 中编写代码。 这样,可以更轻松地检查以确保本机 Python 代码正确无误。

    from random import random
    from time import perf_counter
    
    # Change the value of COUNT according to the speed of your computer.
    # The value should enable the benchmark to complete in approximately 2 seconds.
    COUNT = 500000
    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)')
    
  5. 通过选择 调试>无调试开始 或使用键盘快捷键 Ctrl+F5 来运行程序。

    此时会打开一个命令窗口以显示程序输出。

  6. 在输出中,请注意基准进程报告的时间量。

    在本演练中,基准过程大约需要 2 秒。

  7. 根据需要,调整代码中的变量值 COUNT ,使基准测试在大约 2 秒内在计算机上完成。

  8. 再次运行程序,确认修改 COUNT 后的值在大约 2 秒内生成基准。

小窍门

运行基准测试时,请始终使用 “调试>开始”选项而不使用调试 选项。 此方法有助于避免在 Visual Studio 调试器中运行代码时产生的开销。

创建核心C++项目

按照以下步骤创建两个相同的C++项目: superfastcode 和 superfastcode2。 稍后,在每个项目中使用不同的方法向 Python 公开C++代码。

  1. 在 解决方案资源管理器中,右键单击解决方案名称,然后选择“ 添加新>项目”。

    Visual Studio 解决方案可以同时包含 Python 和C++项目,这是使用 Visual Studio 进行 Python 开发的优点之一。

  2. 在“添加新项目”对话框中,将语言筛选器设置为C++,并在“搜索”框中输入空。

  3. 在项目模板结果列表中,选择 “空项目”,然后选择“ 下一步”。

  4. 在 “配置新项目 ”对话框中,输入 项目名称:

    • 对于第一个项目,请输入名称 superfastcode。
    • 对于第二个项目,请输入名称 superfastcode2。
  5. 选择 创建。

请务必重复这些步骤并创建两个项目。

小窍门

在 Visual Studio 中安装了 Python 本机开发工具时,可以使用替代方法。 可以从 Python 扩展模块 模板开始,该模板预先完成了本文中所述的许多步骤。

对于本文中的演练,从空项目开始,有助于逐步演示如何生成扩展模块。 了解该过程后,可以使用备用模板在编写自己的扩展时节省时间。

将C++文件添加到项目

接下来,将C++文件添加到每个项目。

  1. 在 解决方案资源管理器中,展开项目,右键单击 “源文件 ”节点,然后选择“ 添加新>项”。

  2. 在文件模板列表中,选择“C++文件”(.cpp)。

  3. 输入文件的名称作为module.cpp,然后选择“添加”。

    重要

    请确保文件名包含 .cpp 扩展名。 Visual Studio 查找扩展名 为.cpp 的文件,以便显示C++项目属性页。

  4. 在工具栏上,展开 “配置 ”下拉菜单并选择目标配置类型:

    显示如何在 Visual Studio 中设置C++项目的目标配置类型的屏幕截图。

    • 对于 64 位 Python 运行时,请激活 x64 配置。
    • 对于 32 位 Python 运行时,请激活 Win32 配置。

请务必对两个项目重复这些步骤。

配置项目属性

将代码添加到新的C++文件之前,请配置每个C++模块项目的属性,并测试配置以确保一切正常。

需要为每个模块的 调试 和 发布 生成配置设置项目属性。

  1. 在 解决方案资源管理器中,右键单击C++模块项目(superfastcode 或 superfastcode2),然后选择“ 属性”。

  2. 为模块的 调试 版本配置属性,然后为 发布 版本配置相同的属性:

    在“项目 属性页 ”对话框顶部,配置以下文件配置选项:

    显示如何在 Visual Studio 中设置C++文件的项目生成和平台选项的屏幕截图。

    1. 对于 配置,请选择 “调试 ”或“ 发布”。 你可能会看到这些带有 活动 前缀的选项。

    2. 对于 平台,请选择 “活动”(x64) 或 “活动”(Win32),具体取决于上一步中的选择。

      注释

      创建自己的项目时,需要根据特定的方案要求单独配置 调试 和 发布 配置。 在本练习中,将配置设置为使用 CPython 的发布版本。 此配置禁用 Microsoft Visual C++ Runtime 的某些调试功能,包括断言。 使用 CPython 调试二进制文件(python_d.exe)需要不同的设置。

    3. 设置下表中所述的其他项目属性。

      若要更改属性值,请在属性字段中输入值。 对于某些字段,可以选择当前值以展开选项下拉菜单或打开对话框来帮助定义值。

      更新选项卡上的值后,请在切换到其他选项卡之前选择 “应用 ”。此作有助于确保更改保持不变。

      选项卡和分区 资产 价值
      配置属性>常规 目标名称 指定模块的名称,以便在 Python 语句中(如 from...import 中的 superfastcode)引用。 定义 Python 模块时,请在C++代码中使用此同名。 若要使用项目的名称作为模块名称,请保留默认值 $<ProjectName>。 请在 python_d.exe 名称末尾添加 _d。
      配置类型 动态库 (.dll)
      配置属性>高级 目标文件扩展名 .pyd (Python 扩展模块)
      C/C++>常规 其他包含目录 根据安装情况添加 Python include 文件夹(例如 c:\Python36\include)。
      C/C++>预处理 预处理器定义 如果存在,请将 _DEBUG 值更改为 NDEBUG 以匹配 CPython 的非debug 版本。 使用 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++ 选项卡未显示为项目属性的选项,则项目不包含 Visual Studio 标识为 C/C++ 源文件的代码文件。 如果在没有 .c 或 .cpp 文件扩展名的情况下创建源文件,则可能会出现这种情况。

      如果在创建C++文件时意外输入 了 module.coo 而不是 module.cpp,Visual Studio 会创建该文件,但不会将文件类型设置为 C/C+ 编译器。 此文件类型是激活项目属性对话框中 C/C++ 属性选项卡的存在所必需的。 即使将代码文件重命名为 .cpp 文件扩展名,误识别仍然存在。

      若要正确设置代码文件类型,请在 解决方案资源管理器中右键单击代码文件并选择“ 属性”。 对于 项类型,请选择 C/C++ 编译器。

    4. 更新所有属性后,选择“ 确定”。

    对其他生成配置重复相同的步骤。

  3. 测试当前配置。 对两个C++项目的 调试 和 发布 版本重复以下步骤。

    1. 在 Visual Studio 工具栏上,将 “生成 ”配置设置为 “调试 ”或 “发布”:

      显示如何在 Visual Studio 中设置C++项目的生成配置的屏幕截图。

    2. 在 解决方案资源管理器中,右键单击C++项目,然后选择“ 生成”。

      .pyd 文件位于解决方案文件夹的“调试”和“发布”下,而不是在C++项目文件夹本身中。

添加代码和测试配置

现在,你已准备好将代码添加到C++文件并测试 发布 版本。

  1. 对于 superfastcode C++ 项目,请在代码编辑器中打开 module.cpp 文件。

  2. 在 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);
    }
    
  3. 保存更改。

  4. 生成C++项目的 发布 配置,以确认代码正确。

重复这些步骤,将代码添加到 superfastcode2 项目的 C++ 文件中,并测试 发布 版本。

将C++项目转换为 Python 扩展

若要使 C++ DLL 成为 Python 的扩展,请先修改导出的方法以与 Python 类型交互。 然后,添加一个函数来导出模块,以及模块方法的定义。

以下部分演示如何使用 CPython 扩展和 PyBind11 创建扩展。 superfasctcode 项目使用 CPython 扩展,superfasctcode2 项目实现 PyBind11。

使用 CPython 扩展

有关本节中介绍的代码的详细信息,请参阅 Python/C API 参考手册,尤其是 “模块对象 ”页。 查看参考内容时,请务必在右上角的下拉列表中选择 Python 版本。

  1. 对于 superfastcode C++ 项目,请在代码编辑器中打开 module.cpp 文件。

  2. 在 module.cpp 文件的顶部添加语句以包含 Python.h 头文件:

    #include <Python.h>
    
  3. 将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);
    }
    
  4. 在文件末尾,添加一个结构以定义如何将 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 }
    };
    
  5. 添加另一个结构,以定义如何在 Python 代码中引用模块,尤其是在使用 from...import 语句时。

    此代码中导入的名称应与 “配置属性>常规>目标名称”下的项目属性中的值匹配。

    在如下示例中,由于"superfastcode"在from superfastcode import fast_tanh中定义,fast_tanh名称表示您可以在Python中使用superfastcode_methods语句。 C++项目内部的文件名(如 module.cpp)是无关紧要的。

    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
    };
    
  6. 添加 Python 加载模块时调用的方法。 方法名称必须是 PyInit_<module-name>,其中 <模块名称> 与C++项目的 “配置属性>常规>目标名称” 属性完全匹配。 也就是说,方法名称与项目生成的 .pyd 文件的文件名匹配。

    PyMODINIT_FUNC PyInit_superfastcode() {
        return PyModule_Create(&superfastcode_module);
    }
    
  7. 生成C++项目并验证代码。 如果遇到错误,请参阅 “排查编译错误” 部分。

使用 PyBind11

如果您完成了上一部分中 superfastcode 项目的步骤,您可能会注意到练习需要模板代码来创建用于 C++ CPython 扩展的模块结构。 在本练习中,你发现 PyBind11 简化了编码过程。 在C++头文件中使用宏来完成相同的结果,但代码要少得多。 但是,需要执行额外的步骤来确保 Visual Studio 可以找到 PyBind11 库并包含文件。 有关本节中的代码的详细信息,请参阅 PyBind11 基础知识。

安装 PyBind11

第一步是在项目配置中安装 PyBind11。 在本练习中,你将使用 “开发人员 PowerShell ”窗口。

  1. 打开 “工具>命令行>开发人员 PowerShell ”窗口。

  2. 在 开发人员 PowerShell 窗口中,使用 pip 命令 pip install pybind11 或 py -m pip install pybind11安装 PyBind11。

    Visual Studio 安装 PyBind11 及其依赖包。

将 PyBind11 路径添加到项目

安装 PyBind11 后,需要将 PyBind11 路径添加到项目的 “附加包含目录 ”属性。

  1. 在 开发人员 PowerShell 中,运行命令 python -m pybind11 --includes 或 py -m pybind11 --includes。

    该操作将打印出一个需添加到项目属性中的 PyBind11 路径列表。

  2. 突出显示窗口中的路径列表,然后在窗口工具栏上选择“ 复制 ”(双页)。

    显示如何在 Visual Studio 中的“开发人员 PowerShell”窗口中突出显示和复制路径列表的屏幕截图。

    连接路径的列表将添加到剪贴板。

  3. 在 解决方案资源管理器中,右键单击 superfastcode2 项目,然后选择“ 属性”。

  4. 在“ 属性页 ”对话框顶部的 “配置 ”字段,选择“ 发布”。 (您可能会看到此选项带有 活动 前缀。)

  5. 在对话框中的 C/C++>General 选项卡中,展开 “其他包含目录 ”属性的下拉菜单,然后选择“ 编辑”。

  6. 在弹出对话框中,添加复制的路径列表:

    对从 开发人员 PowerShell 窗口复制的串联列表中的每个路径重复这些步骤:

    1. 在弹出对话框工具栏上选择 “新建行 ”(带加号的文件夹)。

      显示如何将 PyBind11 路径添加到“其他包含目录”属性的屏幕截图。

      Visual Studio 在路径列表顶部添加一个空行,并将插入光标放在开头。

    2. 将 PyBind11 路径粘贴到空行中。

      还可以选择 “更多选项 ”(...),并使用弹出文件资源管理器对话框浏览到路径位置。

      重要

      • 如果路径包含 -I 前缀,请从路径中删除前缀。
      • 若要使 Visual Studio 识别路径,路径需要位于单独的行上。

      添加新路径后,Visual Studio 将在 “评估值 ”字段中显示确认的路径。

  7. 选择 “确定 ”退出弹出对话框。

  8. 在 “属性页 ”对话框顶部,将鼠标悬停在 “其他包含目录” 属性的值上,并确认 PyBind11 路径存在。

  9. 单击确定以应用属性更改。

更新module.cpp文件

最后一步是将 PyBind11 头文件和宏代码添加到项目C++文件。

  1. 对于 superfastcode2 C++项目,请在代码编辑器中打开 module.cpp 文件。

  2. 在 module.cpp 文件的顶部添加语句以包含 pybind11.h 头文件:

    #include <pybind11/pybind11.h>
    
  3. 在 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
    }
    
  4. 生成C++项目并验证代码。 如果遇到错误,请参阅下一部分, 排查编译错误。

排查编译错误

查看以下部分,了解可能导致C++模块生成失败的可能问题。

错误:找不到头文件

Visual Studio 返回错误消息 ,如 E1696:无法打开源文件“Python.h” 或 C1083:无法打开包含文件:“Python.h”:无此类文件或目录。

此错误表示编译器找不到项目所需的标头 (.h) 文件。

  • 对于 superfastcode 项目,请验证 C/C++>General>Additional Include Directory 项目属性是否包含 Python 安装 包含 文件夹的路径。 查看 “配置项目属性”中的步骤。

  • 对于 superfastcode2 项目,请验证同一项目属性是否包含 PyBind11 安装 包含 文件夹的路径。 查看 添加 PyBind 路径的步骤。

有关访问 Python 安装配置信息的详细信息,请参阅 Python 文档。

错误:找不到 Python 库

Visual Studio 返回一个错误,指示编译器找不到项目所需的库 (DLL) 文件。

  • 对于C++项目(superfastcode 或 superfastcode2),请验证 链接器>常规>其他库目录 属性是否包含 Python 安装的 libs 文件夹的路径。 查看 “配置项目属性”中的步骤。

有关访问 Python 安装配置信息的详细信息,请参阅 Python 文档。

Visual Studio 报告与项目的目标体系结构配置相关的链接器错误,例如 x64 或 Win32。

  • 对于C++项目(superfastcode 或 superfastcode2),请更改目标配置以匹配 Python 安装。 例如,如果C++项目目标配置为 Win32,但 Python 安装为 64 位,请将C++项目目标配置更改为 x64。

测试代码并比较结果

将 DLL 结构化为 Python 扩展后,可以从 Python 项目引用它们,导入模块并使用其方法。

使 DLL 可供 Python 使用

可以通过多种方式向 Python 提供 DLL。 以下是需要考虑的两个选项:

如果 Python 项目和C++项目位于同一解决方案中,则可以使用以下方法:

  1. 在 解决方案资源管理器中,右键单击 Python 项目中的 “引用 ”节点,然后选择“ 添加引用”。

    请确保为 Python 项目执行此作,而不是针对C++项目执行此作。

  2. 在 “添加引用 ”对话框中,展开“ 项目 ”选项卡。

  3. 选中 superfastcode 和 superfastcode2 项目的复选框,然后选择“ 确定”。

    显示如何在 Visual Studio 中添加对超级快速代码项目的引用的屏幕截图。

另一种方法是在 Python 环境中安装C++扩展模块。 此方法使该模块可用于其他 Python 项目。 有关详细信息,请参阅 setuptools 项目文档。

完成以下步骤,在 Python 环境中安装 C++ 扩展模块:

  1. 在 解决方案资源管理器中,右键单击C++项目,然后选择“ 添加新>项”。

  2. 在文件模板列表中,选择“C++文件”(.cpp)。

  3. 输入文件的名称作为 setup.py,然后选择“添加”。

    请务必使用 Python(.py)扩展名输入文件名。 尽管使用了C++文件模板,Visual Studio 仍会将该文件识别为 Python 代码。

    Visual Studio 将在代码编辑器中打开新文件。

  4. 将以下代码粘贴到新文件中。 选择与扩展方法对应的代码版本:

    • 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],
      )
      
  5. 在C++项目中,创建名为 pyproject.toml 的第二个文件,并粘贴以下代码:

    [build-system]
    requires = ["setuptools", "wheel", "pybind11"]
    build-backend = "setuptools.build_meta"
    

    TOML (.toml) 文件对配置文件使用 Tom 的“明显、最小语言”格式。

  6. 若要生成扩展,请在代码窗口选项卡中右键单击 pyproject.toml 文件名,然后选择“ 复制完整路径”。

    显示如何在 Visual Studio 中将完整路径复制到 py 项目 toml 文件的屏幕截图。

    在使用该名称之前,请从路径中删除 pyproject.toml 名称。

  7. 在 解决方案资源管理器中,展开解决方案的 Python 环境 节点。

  8. 右键单击活动 Python 环境(以粗体显示),然后选择“ 管理 Python 包”。

    此时会打开 “Python 环境” 窗格。

    如果已安装必要的包,会看到它列在此窗格中。

    • 在继续之前,请选择包名称旁边的 X 以卸载它。

    显示如何在“Python 环境”窗格中卸载包的屏幕截图。

  9. 在 Python 环境 窗格的搜索框中,粘贴复制的路径,并从路径末尾删除 pyproject.toml 文件名。

    显示如何在“Python 环境”窗格中输入路径以安装扩展模块的屏幕截图。

  10. 选择 Enter 可从复制路径的位置安装模块。

    小窍门

    如果安装因权限错误而失败,请将参数添加到 --user 命令末尾,然后重试安装。

从 Python 调用 DLL

将 DLL 提供给 Python 后,如前一部分所述,即可从 Python 调用 superfastcode.fast_tanh 和 superfastcode2.fast_tanh2 函数。 然后,可以将函数性能与 Python 实现进行比较。

按照以下步骤从 Python 调用扩展模块 DLL:

  1. 在代码编辑器中打开 Python 项目的 .py 文件。

  2. 在文件末尾,添加以下代码以调用从 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)')
    
  3. 通过选择 “>开始执行(不调试)” 或使用键盘快捷方式 Ctrl+F5 来运行 Python 程序。

    注释

    如果 “无调试启动 ”命令不可用,请在 解决方案资源管理器中右键单击 Python 项目,然后选择“ 设置为启动项目”。

    当程序执行时,请注意,C++例程的运行速度比 Python 实现快约 5 到 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
    
  4. 请尝试增加 COUNT 变量,以便更明显地显示时间差异。

    C++模块的 调试 版本也运行速度比 发布 版本慢,因为调试生成不太优化,并且包含各种错误检查。 请尝试在生成配置之间进行切换以进行比较,但请记住更新之前为发布配置设置的属性。

解决进程速度和开销问题

在输出中,你可能会注意到 PyBind11 扩展的速度不如 CPython 扩展快,尽管它应该比纯 Python 实现更快。 差异的主要原因是使用 METH_O标志。 此标志不支持多个参数、参数名称或关键字参数。 PyBind11 会生成稍微更复杂的代码,以便为调用方提供更类似于 Python 的接口。 由于测试代码调用函数 50万次,结果可能导致开销大幅增加。

可以通过将 for 循环移动到原生 Python 代码来进一步降低开销。 此方法涉及使用迭代器协议(或py::iterable PyBind11 类型)来处理每个元素。 删除 Python 和 C++ 之间的重复转换是减少处理序列所需的时间的有效方法。

排查导入错误

如果在尝试导入模块时收到消息 ImportError ,可以通过以下方式之一解决它:

  • 通过项目引用生成时,请确保C++项目属性与为 Python 项目激活的 Python 环境匹配。 确认Include(.h)和Library(DLL)文件使用的是相同的文件夹位置。

  • 确保正确命名输出文件,例如 superfastcode.pyd。 不正确的名称或扩展名会阻止导入必要的文件。

  • 如果使用 setup.py 文件安装模块,请确保在为 Python 项目激活的 Python 环境中运行 pip 该命令。 在 解决方案资源管理器中扩展项目的活动 Python 环境时,应会看到C++项目的条目,例如 superfastcode。

调试C++代码

Visual Studio 支持将 Python 和C++代码一起调试。 以下步骤演示 superfastcode C++ 项目的调试过程,不过 superfastcode2 项目的调试过程也是相同的。

  1. 在 解决方案资源管理器中,右键单击 Python 项目,然后选择“ 属性”。

  2. 在“ 属性 ”窗格中,选择 “调试 ”选项卡,然后选择 “调试>启用本机代码调试 ”选项。

    小窍门

    启用本机代码调试时,Python 输出窗口可能会在程序完成后立即关闭,而不会暂停并显示 “按任意键继续 提示”。 若要在启用本机代码调试后强制暂停和提示,请将-i参数添加到“调试”选项卡上的>解释器参数”字段。此参数使 Python 解释器在代码运行后进入交互模式。 程序等待你选择 Ctrl+Z+Enter 关闭窗口。 另一种方法是在 Python 程序末尾添加 import os 和 os.system("pause") 语句。 此代码复制原始暂停提示。

  3. 选择 “文件>保存 ”(或 Ctrl+S)保存属性更改。

  4. 在 Visual Studio 工具栏上,将 “生成 ”配置设置为 “调试”。

  5. 由于代码通常需要更长的时间才能在调试器中运行,因此可能需要将 Python 项目中的 COUNT 变量 .py 文件更改为小于默认值五倍的值。 例如,将其从 500000 更改为 100000。

  6. 在C++代码中,在方法的第一行 tanh_impl 上设置断点。

  7. 选择 “调试>开始调试 ”或使用键盘快捷方式 F5 来启动调试器。

    调用断点代码时,调试器将停止。 如果断点未命中,请检查以确保配置设置为 “调试”,并且确保你已保存项目,因为启动调试器时不会自动保存。

    Visual Studio 中含有断点的 C++ 代码的屏幕截图。

  8. 在断点处,您可以逐步执行C++代码,检查变量等等。 有关这些功能的详细信息,请参阅调试 Python 和C++。

备用方法

可以通过各种方式创建 Python 扩展,如下表所述。 本文将讨论前两行 CPythonPyBind11。

方法 旧式 代表用户
CPython 的 C/C++ 扩展模块 1991 标准库
PyBind11 (建议用于C++) 2015
西松 (推荐用于 C) 2007 gevent、 kivy
HPy 2019
mypyc 2017
ctype 2003 oscrypto
cffi 2013 加密, pypy
SWIG 1996 crfsuite
Boost.Python 2002
cppyy 2017