Python 调用 C 语言代码的核心原理是:Python 解释器(通常是 CPython)本身是用 C 写的,它提供了一套标准的 C API,允许 C 代码作为“扩展模块”被加载到 Python 进程中执行。

这就好比 Python 是一个经理,C 语言是技术专家。经理(Python)可以通过特定的“内部电话”(C API)直接指挥专家(C)干活,甚至让专家直接操作内存。

以下是 Python 调用 C 代码的四种主要方式,按从底层到高层的顺序排列:

1. Python C API (最底层、最原生)

这是 Python 官方提供的标准接口。你需要编写符合 Python 规范的 C 代码,将其编译成动态链接库(.so Linux/Mac 或 .pyd Windows),然后像导入普通 Python 模块一样导入

  • 原理
    • C 代码中包含 #include <Python.h>
    • 定义函数时,必须遵循特定格式(如 static PyObject* my_func(PyObject *self, PyObject *args))。
    • 需要手动处理引用计数(管理内存)和类型转换(把 Python 的 int 转为 C 的 long,反之亦然)。
  • 优点:性能最高,无中间层开销;可以深度操作 Python 内部对象。
  • 缺点开发难度大,代码繁琐,容易因内存管理错误导致崩溃(Segmentation Fault)。
  • 适用场景:编写高性能的基础库(如 NumPy, TensorFlow 的底层)。

代码示例逻辑

// hello.c
#include <Python.h>

static PyObject* say_hello(PyObject* self, PyObject* args) {
    const char* name;
    // 解析 Python 传入的参数
    if (!PyArg_ParseTuple(args, "s", &name))
        return NULL;
    printf("Hello %s!\n", name);
    // 返回 Python 对象 (None)
    Py_RETURN_NONE;
}

// 定义模块方法表
static PyMethodDef Methods[] = {
    {"say_hello", say_hello, METH_VARARGS, "Print a greeting"},
    {NULL, NULL, 0, NULL}
};

// 定义模块结构
static struct PyModuleDef moduledef = {
    PyModuleDef_HEAD_INIT,
    "hello_module",
    NULL,
    -1,
    Methods
};

// 模块初始化入口
PyMODINIT_FUNC PyInit_hello_module(void) {
    return PyModule_Create(&moduledef);
}

编译后在 Python 中:import hello_module; hello_module.say_hello("World")


2. Cython (最流行、平衡性最好)

Cython 是 Python 的超集。你写的是类似 Python 的代码(.pyx 文件),但它可以添加 C 类型的声明。Cython 编译器会将这些代码翻译成优化的 C 代码,然后再编译成 Python 扩展模块。

  • 原理
    • 语法接近 Python,但可以声明 cdef int x 这样的 C 变量。
    • 自动处理 Python 对象与 C 数据的转换。
    • 生成 C 代码并调用 gcc/clang 编译。
  • 优点语法简单(90% 是 Python 语法),性能提升巨大(接近 C),生态成熟。
  • 缺点:需要学习少量的额外语法(cdefcpdef),构建过程稍复杂。
  • 适用场景加速 Python 循环、数值计算、封装现有的 C 库。

代码示例逻辑

python

# hello.pyx
def say_hello(str name):
    # 这一行会被编译成高效的 C 代码
    print(f"Hello {name}!") 
    
    # 纯 C 级别的循环加速
    cdef int i
    for i in range(1000000):
        pass

3. ctypes / cffi (无需编译 C 代码,直接调用现有库)

如果你已经有一个编译好的 C 动态库(.dll.so),不想重新编译,只想在 Python 里调用它,就用这两个。

  • ctypes (标准库自带)
    • 原理:在 Python 运行时动态加载库,手动定义参数类型和返回类型。
    • 优点:无需安装额外包,Python 内置。
    • 缺点:类型定义繁琐,出错时调试困难。
  • cffi (第三方库)
    • 原理:类似 ctypes,但语法更优雅,支持内联 C 代码声明。
    • 优点:比 ctypes 更易用,性能略好,PyPy 支持更好。

ctypes 示例逻辑

python

import ctypes

# 加载已编译好的 C 库
lib = ctypes.CDLL('./libhello.so')

# 告诉 Python 函数的参数类型和返回类型
lib.say_hello.argtypes = [ctypes.c_char_p]
lib.say_hello.restype = None

# 调用
lib.say_hello(b"World") 

4. pybind11 (现代 C++ 首选,也适用于 C)

虽然名字叫 pybind11(主要针对 C++),但它也可以用来封装 C 代码(需包裹一层 C++)。它是现代 C++ 项目连接 Python 的事实标准。

  • 原理利用 C++ 模板元编程,自动生成绑定代码。
  • 优点:语法极其简洁现代,自动处理异常和类型转换,头文件-only(只需包含头文件即可使用)。
  • 缺点主要针对 C++,纯 C 项目需要稍微包装一下。

总结:如何选择?

表格

需求场景 推荐方案 理由
我要加速现有的 Python 代码 Cython 改动最小,性能提升明显,语法友好。
我要调用现成的 C/C++ 库 (.so/.dll) ctypes / cffi 无需重新编译库,直接在 Python 侧对接。
我要开发一个新的底层高性能库 pybind11 (C++) 或 C API (纯 C) 现代项目首选 pybind11;极致控制选 C API。
我只是想简单测试一下 ctypes 不需要安装任何东西,Python 自带。

核心流程图解

无论用哪种方法,数据流向都是:

  1. Python 层:用户调用函数 func(arg)
  2. 桥接层 (Cython/ctypes/C API):
    • 将 Python 对象(如 liststr解包 (Unmarshal) 为 C 的基本数据类型(如 int*char*)。
    • 检查类型是否匹配。
  3. C 层:执行纯粹的 C 逻辑(操作内存、指针运算、硬件交互)。
  4. 桥接层:将 C 的返回结果打包 (Marshal) 回 Python 对象。
  5. Python 层:接收返回值,继续执行后续 Python 代码。

这种机制使得 Python 能够兼具开发的灵活性(上层逻辑)和执行的效率/硬件能力(底层 C 代码)。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐