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

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

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

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

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

  • 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++项目: superfastcodesuperfastcode2。 稍后,在每个项目中使用不同的方法向 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++模块项目(superfastcodesuperfastcode2),然后选择“ 属性”。

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

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

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

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

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

      注释

      创建自己的项目时,需要根据特定的方案要求单独配置 调试发布 配置。 在本练习中,将配置设置为使用 CPython 的发布版本。 此配置禁用C++运行时的某些调试功能,包括断言。 使用 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 语句时。

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

    在如下示例中,由于fast_tanhsuperfastcode_methods中定义,"superfastcode"名称表示您可以在Python中使用from superfastcode import fast_tanh语句。 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 pybind11py -m pip install pybind11安装 PyBind11。

    Visual Studio 安装 PyBind11 及其依赖包。

将 PyBind11 路径添加到项目

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

  1. 开发人员 PowerShell 中,运行命令 python -m pybind11 --includespy -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++项目(superfastcodesuperfastcode2),请验证 链接器>常规>其他库目录 属性是否包含 Python 安装的 libs 文件夹的路径。 查看 “配置项目属性”中的步骤。

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

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

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

测试代码并比较结果

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

使 DLL 可供 Python 使用

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

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

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

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

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

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

    显示如何在 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]
      )
      
    • PyBind11superfastcode2 项目):

      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_tanhsuperfastcode2.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 代码来进一步降低开销。 此方法涉及使用迭代器协议(或函数参数的 PyBind11 py::iterable 类型)来处理每个元素。 删除 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 osos.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 geventkivy
HPy 2019
mypyc 2017
ctype 2003 oscrypto
cffi 2013 加密pypy
SWIG 1996 crfsuite
Boost.Python 2002
cppyy 2017