Python调用C/C++函数库的多种方法与实践指南

发布于:2025-08-12 ⋅ 阅读:(19) ⋅ 点赞:(0)

Python作为一门高级编程语言,以其简洁的语法和丰富的库生态赢得了开发者的青睐。然而,在计算密集型任务中,Python的性能往往无法满足要求。Python调用C/C++函数库成为提升应用性能的关键技术路径,通过将底层计算逻辑用C/C++实现,再通过适当的接口与Python交互,可以在保持开发效率的同时获得接近系统语言的执行性能。本文将深入探讨Python调用C/C++函数库的多种方法,包括它们的工作原理、实现步骤、优缺点及适用场景,帮助开发者根据具体需求选择最适合的集成方案。

一、主流调用方法及其基本原理

Python调用C/C++函数库主要有五种主流方法,各有其独特的实现机制和适用范围:

ctypes是Python标准库的一部分,提供了一种直接与C语言兼容的数据类型和函数调用方式。它通过动态加载共享库(.so或.dll)并在内存中创建对应的函数指针来实现调用,无需任何预编译步骤。ctypes的工作原理类似于Windows平台上的DLL调用或Linux平台上的动态链接库加载,通过函数指针和参数类型转换直接调用C函数。

SWIG(SimplifiedWrapper and Interface Generator)是一个开源的软件接口生成器,能够自动生成C/C++与Python之间的接口代码。SWIG通过解析C/C++头文件和接口描述文件(.i),生成特定语言的包装代码,这些代码负责在Python和C/C++之间进行参数转换和内存管理。SWIG支持多种语言和复杂的C++特性,如类、继承、模板等,是处理大型C++项目时的常用工具。

cffi(C Foreign Function Interface)是另一个Python库,用于调用C语言函数。与ctypes不同,cffi采用声明式接口,开发者可以使用接近C语法的声明来定义函数和数据结构。cffi的优势在于其可读性和维护性,同时它还支持与PyPy解释器的兼容,这在某些高性能场景中非常有价值。

Python/C API是Python官方提供的扩展接口,允许开发者用C/C++编写Python模块。这种方法需要开发者深入理解Python对象模型和内存管理机制,通过一系列宏和函数(如PyMOD初始化、PyMethodDef定义等)手动创建Python可调用的对象和方法。Python/C API提供了最大的灵活性,但开发复杂度也最高。

pybind11是一个现代的C++库,旨在简化C++与Python之间的绑定过程。它通过C++模板和编译时类型推导,将C++代码无缝暴露给Python,无需手动编写大量包装代码。pybind11支持C++11及更高版本的特性,如智能指针、移动语义等,是当前推荐的C++与Python集成方案。

二、实现步骤与代码示例

1. ctypes方法

ctypes是最简单的调用方式,适合快速原型开发和简单函数调用。实现步骤如下:

编写C/C++函数并导出为共享库

// add.c
#include <stdio.h>

int add_int(int a, int b) {
    printf("Adding %d and %d\n", a, b);
    return a + b;
}

float add_float(float a, float b) {
    printf("Adding %f and %f\n", a, b);
    return a + b;
}

编译为共享库:

gcc -shared -Wl,-soname,adder -o adder.so -fPIC add.c

Python调用代码

import ctypes

# 加载共享库
adder = ctypes.CDLL('./adder.so')

# 设置函数参数类型和返回值类型
adder.add_int.argtypes = [ctypes.c_int, ctypes.c_int]
adder.add_int.restype = ctypes.c_int

adder.add_float.argtypes = [ctypes.c_float, ctypes.c_float]
adder.add_float支柱 = ctypes.c_float

# 调用函数
result_int = adder.add_int(4, 5)
print(f"4 + 5 = {result_int}")

a = ctypes.c_float(5.5)
b = ctypes.c_float(4.1)
result_float = adder.add_float(a, b)
print(f"5.5 + 4.1 = {result_float}")

C++类调用:由于C++支持函数重载,编译器会修改函数名,ctypes无法直接调用。解决方法是在C++代码中使用extern “C”声明:

// TestLib.cpp
#include <iostream>

extern "C" {
    class TestLib {
    public:
        void display() {
            std::cout << "First display" << std::endl;
        }

        void display(int a) {
            std::cout << "Second display:" << a << std::endl;
        }
    };

    TestLib global_obj;

    void display() {
        global_obj.display();
    }

    void display_int(int a) {
        global_obj.display(a);
    }
}

编译命令:

g++ -shared -fPIC TestLib.cpp -o libTestLib.so

Python调用:

import ctypes

lib = ctypes.CDLL('./libTestLib.so')

lib.display()
lib.display_int(100)
2. cffi方法

cffi提供了更接近C语法的接口定义,适合需要在Python中嵌入C代码的场景。实现步骤如下:

编写C/C++函数并导出为共享库

// ffi_test.cpp
extern "C" {
    int add(int a, int b) {
        return a + b;
    }
}

编译命令:

g++ffi_test.cpp -fPIC -shared -o libffi_test.so

Python调用代码

from cffi import FFI

ffi = FFI()
# 声明C函数接口
ffi.cdef(
    """int add(int a, int b);"""
)

# 加载共享库
lib = ffi.dlopen('libffi_test.so')

# 调用函数
print(f"3 + 4 = {lib.add(3, 4)}")

Python代码中嵌入C代码(无需编译为共享库):

from cffi import FFI

ffi = FFI()
# 声明C函数接口
ffi.cdef(
    """int add(int a, int b);"""
)

# 定义并编译C代码
lib =ffi.verify(
    """
    int add(int a, int b) {
        return a + b;
    }
    """
)

print(f"3 + 4 = {lib.add(3, 4)}")
3. SWIG方法

SWIG适合处理复杂的C++项目,能够自动处理类、继承、模板等特性。实现步骤如下:

编写C++类和接口文件

// TestLib.h
#include <iostream>

class TestLib {
public:
    TestLib();
    ~TestLib();

    void display();
    void display(int a);
    int add(int a, int b);
    float add(float a, float b);
};

// TestLib.cpp
#include "TestLib.h"

TestLib::TestLib() {
    std::cout << "TestLib created" << std::endl;
}

TestLib::~TestLib() {
    std::cout << "TestLib destroyed" << std::endl;
}

void TestLib::display() {
    std::cout << "First display" << std::endl;
}

void TestLib::display(int a) {
    std::cout << "Second display:" << a << std::endl;
}

int TestLib::add(int a, int b) {
    return a + b;
}

float TestLib::add(float a, float b) {
    return a + b;
}

// TestLib.i
%module TestLib

%{
#include "TestLib.h"
%}

// 声明C++类
class TestLib {
public:
    TestLib();
    ~TestLib();
    void display();
    void display(int a);
    int add(int a, int b);
    float add(float a, float b);
};

编译SWIG接口

swig -c++ -python TestLib.i
g++ -fPIC -c TestLib.cpp
g++ -fPIC -c TestLib wrap.cpp
g++ -shared TestLib.o TestLib wrap.o -o _TestLib.so

Python调用代码

import TestLib

# 创建C++对象
obj = TestLib.TestLib()

# 调用成员函数
obj.display()
obj.display(100)

# 调用重载函数
print(f"5 + 3 = {obj.add(5, 3)}")
print(f"2.5 + 3.1 = {obj.add(2.5, 3.1)}")
4. Python/C API方法

Python/C API提供了最大的灵活性,但开发复杂度也最高。实现步骤如下:

编写C++扩展模块

// module.cpp
#include <Python.h>
#include <iostream>

// C++类
class TestLib {
public:
    void display() {
        std::cout << "First display" << std::endl;
    }

    void display(int a) {
        std::cout << "Second display:" << a << std::endl;
    }

    int add(int a, int b) {
        return a + b;
    }

    float add(float a, float b) {
        return a + b;
    }
};

// Python类型定义
static PyTypeObject TestLibType = {
    PyVarObjectHead_init
    "TestLib",
    sizeof(TestLib),
    0,
    // 方法表
    .tpMethods = methods,
};

// 方法定义
static PyMethodDef methods[] = {
    {"display", (PyCFunction)display, METH_VARARGS, "Display message"},
    {"display_int", (PyCFunction)display_int, METH_VARARGS, "Display integer"},
    {"add", (PyCFunction)add, METH_VARARGS, "Add two numbers"},
    {NULL, NULL, 0, NULL}
};

// 模块初始化函数
static struct PyModuleDef moduledef = {
    PyModuleDefHead_init
    "module",
    NULL,
    0,
    .mMethods = NULL,
    .mModuleInit = PyMODInit_module
};

PyMODInit_t PyMODInit_module() {
    // 初始化Python解释器
    Py_Initialize();

    // 创建模块
    PythonModule* module = PyModuleDefInit(&moduledef);

    // 创建类型
    if (PyType_Ready(&TestLibType) < 0)
        return NULL;

    // 将类型添加到模块
    PyModuleDictSetItemStr(module-> ob_type, "TestLib", (PyObject*) &TestLibType);

    return module;
}

编写setup.py

from setuptools import setup, Extension

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

setup(name='module',
      version='1.0',
      description='Python C++ extension',
      ext_modules=[module])

编译扩展模块

python setup.py build_ext --inplace

Python调用代码

import module

# 创建C++对象
obj = module.TestLib()

# 调用成员函数
obj.display()
obj.display_int(100)

# 调用函数
print(f"5 + 3 = {obj.add(5, 3)}")
print(f"2.5 + 3.1 = {obj.add(2.5, 3.1)}")
5. pybind11方法

pybind11是目前推荐的现代C++与Python集成方案,实现步骤如下:

编写C++代码和绑定

// example.cpp
#include <pybind11/pybind11.h>

using namespace pybind11;

class TestLib {
public:
    void display() {
        std::cout << "First display" << std::endl;
    }

    void display(int a) {
        std::cout << "Second display:" << a << std::endl;
    }

    int add(int a, int b) {
        return a + b;
    }

    float add(float a, float b) {
        return a + b;
    }
};

PYBIND11_MODULE(example, m) {
    // 绑定C++类到Python
    m.def("add_int", &TestLib::add, "Add two integers");
    m.def("add_float", &TestLib::add, "Add two floats");

    // 绑定类到Python
    py::class_<TestLib>(m, "TestLib")
        .def py::init<>())
        .def("display", &TestLib::display)
        .def("display_int", &TestLib::display_int);
}

编译扩展模块

c++ -O3 -Wall -shared -std=c++11 -fPIC $(python3 -m pybind11 --includes) example.cpp -o example$(python3-config --extension-suffix)

Python调用代码

import example

# 创建C++对象
obj = example.TestLib()

# 调用成员函数
obj.display()
obj.display_int(100)

# 调用函数
print(f"5 + 3 = {example.add_int(5, 3)}")
print(f"2.5 + 3.1 = {example.add_float(2.5, 3.1)}")

三、不同方法的优缺点与适用场景

下表对五种主流方法进行了系统性比较:

方法

开发复杂度

性能

语言支持

维护性

适用场景

ctypes

C函数

快速原型开发、简单函数调用

cffi

C函数

需要PyPy兼容、轻量级C调用

SWIG

中高

C++类、继承、模板

大型C++项目、复杂接口

Python/C API

最高

C++类、继承、模板

需要深度定制、高性能需求

pybind11

C++11及以上

现代C++项目、简洁接口

ctypes的优势在于简单易用,无需额外编译步骤,适合快速验证C函数调用。但其缺点也很明显:不支持C++类和重载,需要手动处理参数类型和内存管理,难以处理复杂数据结构。

cffi相比ctypes提供了更好的可读性和维护性,支持更接近C语法的声明方式,同时兼容PyPy解释器。这对于需要在不同Python实现上运行的程序非常有价值。然而,cffi同样不支持C++类和重载,需要通过C接口间接调用C++功能。

SWIG是处理复杂C++项目的强大工具,能够自动生成Python绑定代码,支持类、继承、模板等高级特性。SWIG的灵活性使其成为许多大型项目的首选。然而,SWIG的学习曲线较陡,需要编写接口描述文件,并且对于某些C++语法(如非常规类型声明)的支持有限。

Python/C API提供了最大的灵活性和控制权,允许开发者完全定制Python与C/C++之间的交互。这对于需要深度集成或特殊性能优化的场景非常有价值。然而,Python/C API的开发复杂度最高,需要深入理解Python对象模型和内存管理机制,维护成本也相应较高。

pybind11是目前推荐的现代C++与Python集成方案,它结合了SWIG的灵活性和Python/C API的性能优势,同时简化了开发流程。pybind11支持C++11及以上版本的特性,如智能指针、移动语义等,使得C++代码可以保持现代风格。此外,pybind11还提供了丰富的文档和示例,降低了学习曲线。然而,pybind11需要C++11及以上版本的编译器支持,这对于某些遗留系统可能构成限制。

四、性能比较与选择建议

在实际应用中,性能是选择调用方法的重要考量因素。根据多个测试案例,不同方法的性能表现如下:

ctypes的调用开销约为纯C函数的1.5-2倍,适合简单的函数调用和快速原型开发。对于需要频繁调用的函数,ctypes的性能可能无法满足要求。

cffi在性能上与ctypes相当,但在代码可读性和维护性方面有所优势。此外,cffi在PyPy解释器上的表现通常优于ctypes,这对于需要高性能且与PyPy兼容的场景非常有价值。

SWIG生成的绑定代码在性能上接近原生C/C++调用,但需要额外的编译步骤。对于复杂的C++项目,SWIG的性能开销通常可以忽略不计。

Python/C API提供了最高的性能,几乎与纯C/C++调用相当,适合对性能要求极高的场景。然而,其开发复杂度也最高,需要开发者深入理解Python内部机制。

pybind11在性能上与Python/C API相当,但开发复杂度显著降低。这对于大多数现代C++项目来说是一个理想的选择,可以在保持高性能的同时减少开发和维护成本。

基于上述分析,以下是针对不同场景的调用方法选择建议:

对于简单函数调用和快速原型开发,ctypes是最佳选择,其简单易用的特性可以快速验证C函数调用。

对于需要PyPy兼容或轻量级C调用的场景,cffi是一个更好的选择,它提供了更好的可读性和维护性。

对于大型C++项目或复杂接口,SWIG是首选工具,它能够自动生成Python绑定代码,支持类、继承、模板等高级特性。

对于需要深度定制或特殊性能优化的场景,Python/C API提供了最大的灵活性,但需要开发者具备深入的Python和C/C++知识。

对于现代C++项目,pybind11是当前推荐的方案,它结合了SWIG的灵活性和Python/C API的性能优势,同时简化了开发流程,支持C++11及以上版本的特性。

五、实际应用案例与最佳实践

科学计算与数据分析:在RPLIDAR数据通讯和解析项目中,使用C++编写数据解析模块,将运算结果写入共享内存块,再通过Python进行数据处理和可视化。这种方法将C++的高性能与Python的易用性相结合,显著提升了程序的执行效率。

机器学习与深度学习:在CGAL(计算几何算法库)的Python绑定中,使用SWIG自动生成Python接口,同时保留C++的模板特性和高性能。这种方法使得复杂的几何计算可以在Python环境中高效执行,同时保持代码的可维护性和扩展性。

Web开发与系统集成:在Flask框架下集成模拟机接口(如RINSIM平台的Dataport.dll),使用pybind11对C++函数进行封装,生成Python模块。这种方法使得复杂的工业控制逻辑可以在Python Web应用中高效调用,同时保持代码的简洁性和可读性。

最佳实践

  1. 明确需求:在选择调用方法之前,明确项目的具体需求,包括性能要求、复杂度、维护周期等。
  2. 简化接口:无论使用哪种方法,都应尽量简化C/C++与Python之间的接口,减少跨语言调用的开销。
  3. 处理内存管理:跨语言调用需要特别注意内存管理,避免内存泄漏或无效指针访问。
  4. 利用现代C++特性:在使用pybind11等现代工具时,充分利用C++11及以上版本的特性,如智能指针、移动语义等,简化代码并提高安全性。
  5. 测试与验证:在实际应用中,充分测试跨语言调用的性能和稳定性,确保接口的正确性和可靠性。

六、未来发展趋势与技术展望

随着Python和C++生态的不断发展,Python调用C/C++函数库的技术也在持续演进:

类型系统增强:现代工具如pybind11和SWIG正在不断改进对C++类型系统的支持,使得C++的复杂数据结构可以在Python中更自然地表示和操作。

编译器支持改进:C++编译器(如GCC、Clang)对Python绑定的支持也在不断提升,使得C++代码与Python的集成更加无缝。

多解释器兼容:随着PyPy等替代Python解释器的普及,cffi等工具对多解释器的兼容性变得越来越重要,未来可能会有更多工具支持这一特性。

自动化绑定生成:随着AI技术的发展,可能会出现更智能的绑定生成工具,能够自动分析C/C++代码并生成Python接口,进一步降低开发复杂度。

跨平台支持:Python应用通常需要跨平台运行,未来工具可能会更好地支持Windows、Linux、macOS等不同平台的C/C++库调用。

性能优化:随着计算需求的不断提升,工具可能会提供更精细的性能优化选项,如缓存机制、并行计算支持等。

结论:Python调用C/C++函数库是提升应用性能的重要技术路径。根据项目需求、开发复杂度、性能要求和维护周期等因素,可以选择合适的调用方法。对于大多数现代C++项目,pybind11是当前推荐的方案,它结合了SWIG的灵活性和Python/C API的性能优势,同时简化了开发流程。而对于简单的函数调用和快速原型开发,ctypes或cffi可能是更好的选择。无论选择哪种方法,都应遵循最佳实践,确保接口的正确性和可靠性,同时充分利用现代C++和Python特性,提升代码质量和性能。

参考来源:

1. 【py-22】python调用C/C++的几种方法

2. Python调用C/C++-阿里云开发者社区

3. 简单的Python调用C++程序-腾讯云开发者社区-腾讯云

4. Python调用C/C++代码-CSDN博客

5. Python怎么实现调用c/cpp的库?

6. 简单的Python调用C/C++程序的方法_python 调用c++-CSDN博客

7. Linux平台Python调用C/C++代码四种方法案例解析-CSDN文库

8. python3调用c++动态库(linux)

9. Python使用Ctypes调用C/C++动态库_python调用c++库-CSDN博客

10. python调用C++库-CSDN博客

11. DeePMD-kit v2: A software package for Deep Potential models

12. SWIG教程《二》知乎

13. C++和python的代码如何相互调用?

14. swig支持WebAssembly的embind绑定

15. python cffi如何调试?知乎

16. SWIG教程《二》知乎

17. Python 通过FFI调用C/C++代码

18. 为Python编写C++扩展-Visual Studio(Windows)Microsoft Learn

19. swig支持WebAssembly的embind绑定

20. Linux平台Python调用C/C++代码四种方法案例解析-CSDN文库

21. SWIG教程《二》知乎

22. python调用C++的方法各有什么优势,哪个最好?知乎

23. 为Python编写C++扩展-Visual Studio(Windows)Microsoft Learn

24. swig支持WebAssembly的embind绑定

25. Python 通过FFI调用C/C++代码

26. Reticula: A temporal network and hypergraph analysis software package

27. A New Parallel Algorithm for Sinkhorn Word-Movers Distance and Its Performance on PIUMA and Xeon CPU

28. 深度解析C++与Python的交互机制-编程语言-亿速云

29. CGAL Made More Accessible


网站公告

今日签到

点亮在社区的每一天
去签到