杰瑞科技汇

Python如何调用C代码?

调用 C 代码主要有以下几种方法,我会按照从简单到复杂的顺序介绍:

Python如何调用C代码?-图1
(图片来源网络,侵删)
  1. ctypes (最简单): Python 内置库,无需编译,直接加载动态链接库(如 .so.dll)。
  2. CFFI (推荐): C Foreign Function Interface,一个现代、灵活的第三方库,可以处理 C 代码和 Python 的交互。
  3. Python C API (最强大): 编写一个 C 扩展,将 C 代码编译成 Python 可以直接 import 的模块,这是最底层、性能最高的方式。

使用 ctypes (最简单直接)

ctypes 允许你在 Python 中调用动态链接库(如 Windows 的 .dll 或 Linux/macOS 的 .so)中的函数,它非常适合调用已经存在的 C 库。

步骤:

  1. 编写 C 代码并编译成动态库
  2. 在 Python 中使用 ctypes 加载该库并调用函数

示例:

C 代码 (mylib.c)

// mylib.c
#include <stdio.h>
// 一个简单的加法函数
int add(int a, int b) {
    return a + b;
}
// 一个打印字符串的函数
void print_message(const char* message) {
    printf("C 函数收到消息: %s\n", message);
}

编译成动态库

  • 在 Linux/macOS 上:

    # -fPIC 生成位置无关代码,-shared 生成共享库
    gcc -fPIC -shared -o mylib.so mylib.c

    这会生成一个 mylib.so 文件。

  • 在 Windows 上:

    # /LD 生成动态链接库 (.dll)
    cl /LD mylib.c

    这会生成 mylib.dll 和相关的 .lib 文件。

Python 代码 (main.py)

import ctypes
import os
# 加载动态库
# Windows上用 'mylib.dll',Linux/macOS上用 './mylib.so'
lib_name = "mylib.dll" if os.name == 'nt' else "./mylib.so"
mylib = ctypes.CDLL(lib_name)
# --- 调用 add 函数 ---
# 1. 告诉ctypes add函数的参数类型和返回类型
mylib.add.argtypes = [ctypes.c_int, ctypes.c_int]
mylib.add.restype = ctypes.c_int
# 2. 调用函数
result = mylib.add(10, 20)
print(f"10 + 20 = {result}")
# --- 调用 print_message 函数 ---
# 1. 告诉ctypes print_message的参数类型
#    c_char_p 对应 C 中的 char*
mylib.print_message.argtypes = [ctypes.c_char_p]
# 2. 调用函数
#    Python的字符串需要编码成C的char*
message = "Hello from Python!"
mylib.print_message(message.encode('utf-8'))

运行 python main.py 的输出:

10 + 20 = 30
C 函数收到消息: Hello from Python!

ctypes 的优缺点:

  • 优点:
    • Python 内置,无需安装额外依赖。
    • 使用简单,适合快速集成现有的 C 库。
  • 缺点:
    • 性能开销比其他方法稍高。
    • 对于复杂的数据结构(如结构体、数组)处理起来比较繁琐。
    • 编译过程需要手动完成,不如 CFFIC API 集成度高。

使用 CFFI (推荐,现代且灵活)

CFFI (C Foreign Function Interface) 是一个功能强大的第三方库,它允许你直接在 Python 代码中嵌入 C 代码,或者指定外部 C 文件,然后由 CFFI 自动处理编译和加载。

步骤:

  1. 安装 CFFI
  2. 编写一个 cdef 文件,定义 C 函数的接口。
  3. 在 Python 代码中加载并调用

示例:

安装 CFFI

pip install cffi

C 代码 (mylib_cffi.c) - 可以和 ctypes 的例子一样。

// mylib_cffi.c
#include <stdio.h>
int add(int a, int b) {
    return a + b;
}
void print_message(const char* message) {
    printf("CFFI 调用: %s\n", message);
}

Python 代码 (main_cffi.py)

from cffi import FFI
# 1. 定义 C 函数的接口
ffi = FFI()
# cdef 部分告诉 CFFI 我们要调用哪些C函数,以及它们的参数和返回类型
ffi.cdef("""
    int add(int a, int b);
    void print_message(const char *message);
""")
# 2. 加载并编译 C 代码
#    这会自动编译 mylib_cffi.c 并加载生成的库
#    'sources' 参数指定要编译的C源文件
#    'libraries' 和 'library_dirs' 可选,用于链接其他库
lib = ffi.dlopen("./mylib_cffi.so") # Linux/macOS
# lib = ffi.dlopen("mylib_cffi.dll") # Windows
# --- 调用函数 ---
# add 函数
result = lib.add(100, 200)
print(f"100 + 200 = {result}")
# print_message 函数
# CFFI 会自动处理 Python 字符串到 C char* 的转换
message = "Hello from CFFI!"
lib.print_message(message)

运行 python main_cffi.py 的输出:

100 + 200 = 300
CFFI 调用: Hello from CFFI!

CFFI 的优缺点:

  • 优点:
    • 自动化编译:无需手动 gcc,非常方便。
    • 性能好:接近 C API 的性能。
    • 功能强大:对 C 的数据结构支持非常好。
    • 灵活:可以处理外部库或内联 C 代码。
  • 缺点:
    • 需要安装第三方库。
    • ctypes 多一个配置步骤(定义 ffi.cdef)。

使用 Python C API (最强大,最复杂)

这是最底层的方法,你需要用 C 语言编写一个 Python 模块,编译后,这个 C 模块就可以像普通的 Python 模块一样被 import

这种方法通常用于:

  • 对性能要求极高的核心算法。
  • 深度集成到 Python 解释器中。
  • 创建新的 Python 内建类型或对象。

示例:

C 代码 (mymodule.c)

// mymodule.c
#include <Python.h>
// 定义一个函数
static PyObject* add(PyObject* self, PyObject* args) {
    int a, b;
    // 解析传入的参数,格式为 "ii" (两个整数)
    if (!PyArg_ParseTuple(args, "ii", &a, &b)) {
        return NULL; // 参数解析失败,PyArg_ParseTuple 会抛出异常
    }
    // 创建一个 Python 整数对象作为返回值
    return PyLong_FromLong(a + b);
}
// 定义模块中包含的函数列表
static PyMethodDef MyModuleMethods[] = {
    {"add", add, METH_VARARGS, "Add two integers"},
    {NULL, NULL, 0, NULL} // 结束标记
};
// 定义模块本身
static struct PyModuleDef mymodule = {
    PyModuleDef_HEAD_INIT,
    "mymodule", // 模块名
    NULL,       // 模块文档
    -1,
    MyModuleMethods
};
// 模块初始化函数,Python 解释器在导入模块时会调用这个函数
PyMODINIT_FUNC PyInit_mymodule(void) {
    return PyModule_Create(&mymodule);
}

编译成 Python 扩展

你需要一个 setup.py 文件来编译 C 代码。

setup.py

from setuptools import setup, Extension
# 定义 C 扩展模块
module = Extension('mymodule',
                  sources=['mymodule.c'], # 指定C源文件
                  extra_compile_args=['-O2']) # 可以添加编译选项
setup(
    name='MyModule',
    version='1.0',
    description='A simple C extension for Python',
    ext_modules=[module]
)

编译命令:

python setup.py build_ext --inplace

这会在当前目录下生成一个 mymodule.so (Linux/macOS) 或 mymodule.pyd (Windows) 文件。

Python 代码 (main_api.py)

你可以像导入普通 Python 模块一样使用它了。

import mymodule
# 直接调用 add 函数
result = mymodule.add(1000, 2000)
print(f"1000 + 2000 = {result}")

运行 python main_api.py 的输出:

1000 + 2000 = 3000

Python C API 的优缺点:

  • 优点:
    • 最高性能:没有额外的调用层开销。
    • 无缝集成:可以创建新的 Python 类型,操作 Python 对象。
    • 最灵活:可以访问 Python 解释器的内部。
  • 缺点:
    • 非常复杂:学习曲线陡峭,需要深入理解 C 和 Python 的内部机制。
    • 繁琐:编写、编译、调试的流程比前两种方法复杂得多。
    • 可移植性差:编译好的模块通常与 Python 版本和操作系统强相关。

总结与选择建议

方法 难度 性能 集成度 适用场景
ctypes 快速调用现有的、简单的 C 动态库。
CFFI 推荐,需要编写新 C 代码来优化 Python 性能,希望流程自动化。
Python C API 极高 极高 极高 对性能有极致要求,或需要深度定制 Python 解释器/对象。

给你的建议:

  • 新手或简单任务:从 ctypes 开始,如果只是想调用一两个现成的 C 函数,它最快。
  • 绝大多数情况:选择 CFFI,它在易用性、性能和自动化之间取得了最佳平衡,是目前编写 Python C 扩展的主流推荐方式。
  • 专家或特殊需求:当 CFFI 无法满足(你需要创建一种新的、Python 原生不存在的数据类型),或者你正在开发高性能计算库的核心时,才考虑 Python C API
分享:
扫描分享到社交APP
上一篇
下一篇