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

ctypes(最简单): Python 内置库,无需编译,直接加载动态链接库(如.so或.dll)。CFFI(推荐): C Foreign Function Interface,一个现代、灵活的第三方库,可以处理 C 代码和 Python 的交互。Python C API(最强大): 编写一个 C 扩展,将 C 代码编译成 Python 可以直接import的模块,这是最底层、性能最高的方式。
使用 ctypes (最简单直接)
ctypes 允许你在 Python 中调用动态链接库(如 Windows 的 .dll 或 Linux/macOS 的 .so)中的函数,它非常适合调用已经存在的 C 库。
步骤:
- 编写 C 代码并编译成动态库。
- 在 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 库。
- 缺点:
- 性能开销比其他方法稍高。
- 对于复杂的数据结构(如结构体、数组)处理起来比较繁琐。
- 编译过程需要手动完成,不如
CFFI或C API集成度高。
使用 CFFI (推荐,现代且灵活)
CFFI (C Foreign Function Interface) 是一个功能强大的第三方库,它允许你直接在 Python 代码中嵌入 C 代码,或者指定外部 C 文件,然后由 CFFI 自动处理编译和加载。
步骤:
- 安装
CFFI。 - 编写一个
cdef文件,定义 C 函数的接口。 - 在 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。
