Python调用C:一次跨界的完美碰撞

在编程的世界里,Python以其简洁易懂的语法和强大的生态系统深受程序员的喜爱。然而,当我们谈到性能时,Python并不是最快的。有时候,我们需要在Python中调用C代码,来提升性能或利用现有的C库。本文将带你深入了解Python调用C的各种方法,从基础到高级,一步步揭开跨界的奥秘。

为什么要调用C?

在开始之前,我们先来聊聊为什么需要在Python中调用C。主要有以下几个原因:

  1. 性能提升:C语言以其高效的执行速度著称。对于一些计算密集型的任务,使用C代码可以显著提升性能。
  2. 利用现有库:许多成熟的库和框架都是用C或C++编写的,如OpenCV、NumPy等。通过调用这些库,可以避免重复造轮子。
  3. 资源访问:某些低级别的系统资源(如硬件接口)只能通过C来访问。

方法一:ctypes

什么是ctypes?

ctypes 是Python标准库中的一个模块,用于调用C函数和使用C数据类型。它允许你在Python中直接调用动态链接库(DLL)或共享对象(.so)中的函数。

基本用法

假设我们有一个C函数 add,它接受两个整数参数并返回它们的和。我们可以将这个函数编译成一个动态链接库,然后在Python中调用它。

C代码(add.c)

1
2
3
4
5
#include <stdio.h>

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

编译成动态链接库

在Linux上,可以使用以下命令编译:

1
gcc -shared -o libadd.so -fPIC add.c

在Windows上,可以使用以下命令编译:

1
gcc -shared -o add.dll add.c

Python代码

1
2
3
4
5
6
7
8
9
10
11
12
13
import ctypes

# 加载动态链接库
lib = ctypes.CDLL('./libadd.so') # Linux
# lib = ctypes.CDLL('./add.dll') # Windows

# 定义函数的参数类型和返回类型
lib.add.argtypes = (ctypes.c_int, ctypes.c_int)
lib.add.restype = ctypes.c_int

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

高级用法

ctypes 还支持更复杂的数据类型和函数调用。例如,传递结构体、数组等。

C代码(struct_example.c)

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>

typedef struct {
int x;
int y;
} Point;

Point create_point(int x, int y) {
Point p;
p.x = x;
p.y = y;
return p;
}

编译成动态链接库

1
gcc -shared -o libstruct_example.so -fPIC struct_example.c

Python代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import ctypes

# 定义结构体
class Point(ctypes.Structure):
_fields_ = [("x", ctypes.c_int), ("y", ctypes.c_int)]

# 加载动态链接库
lib = ctypes.CDLL('./libstruct_example.so')

# 定义函数的参数类型和返回类型
lib.create_point.argtypes = (ctypes.c_int, ctypes.c_int)
lib.create_point.restype = Point

# 调用函数
point = lib.create_point(10, 20)
print(f"Point: ({point.x}, {point.y})")

方法二:CFFI

什么是CFFI?

cffi(C Foreign Function Interface)是一个更现代的库,用于在Python中调用C代码。它比 ctypes 更强大,支持更复杂的用法,如直接在Python中编写C代码。

基本用法

安装cffi

1
pip install cffi

Python代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import cffi

ffi = cffi.FFI()

# 定义C函数
ffi.cdef("""
int add(int a, int b);
""")

# 加载动态链接库
C = ffi.dlopen("./libadd.so") # Linux
# C = ffi.dlopen("./add.dll") # Windows

# 调用函数
result = C.add(3, 5)
print(f"3 + 5 = {result}")

高级用法

cffi 支持直接在Python中编写C代码,这对于快速原型开发非常有用。

Python代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import cffi

ffi = cffi.FFI()

# 定义C函数
ffi.cdef("""
int add(int a, int b);
""")

# 编写C代码
ffi.set_source("_add", """
int add(int a, int b) {
return a + b;
}
""")

# 编译C代码
ffi.compile()

# 导入编译后的模块
from _add import lib

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

方法三:Cython

什么是Cython?

Cython 是一种将Python代码编译成C代码的工具,生成的C代码可以编译成动态链接库,然后在Python中调用。Cython不仅支持调用C代码,还支持编写混合Python和C的代码,以获得更好的性能。

基本用法

安装Cython

1
pip install Cython

编写Cython代码(add.pyx)

1
2
cdef int add(int a, int b):
return a + b

编写设置文件(setup.py)

1
2
3
4
5
6
from setuptools import setup
from Cython.Build import cythonize

setup(
ext_modules=cythonize("add.pyx")
)

编译Cython代码

1
python setup.py build_ext --inplace

Python代码

1
2
3
4
import add

result = add.add(3, 5)
print(f"3 + 5 = {result}")

高级用法

Cython 支持更复杂的用法,如类型注解、C结构体和数组等。

编写Cython代码(struct_example.pyx)

1
2
3
4
5
6
7
8
9
cdef extern from "struct_example.h":
ctypedef struct Point:
int x
int y
Point create_point(int x, int y)

def create_point(int x, int y):
cdef Point p = create_point(x, y)
return p.x, p.y

编写头文件(struct_example.h)

1
2
3
4
5
6
typedef struct {
int x;
int y;
} Point;

Point create_point(int x, int y);

编写C代码(struct_example.c)

1
2
3
4
5
6
7
8
#include "struct_example.h"

Point create_point(int x, int y) {
Point p;
p.x = x;
p.y = y;
return p;
}

编写设置文件(setup.py)

1
2
3
4
5
6
7
8
9
from setuptools import setup
from Cython.Build import cythonize

setup(
ext_modules=cythonize("struct_example.pyx"),
include_dirs=["."],
libraries=["struct_example"],
library_dirs=["."],
)

编译Cython代码

1
2
3
gcc -c -fPIC struct_example.c -o struct_example.o
gcc -shared struct_example.o -o libstruct_example.so
python setup.py build_ext --inplace

Python代码

1
2
3
4
import struct_example

x, y = struct_example.create_point(10, 20)
print(f"Point: ({x}, {y})")

总结

在Python中调用C代码有多种方法,每种方法都有其适用的场景和优缺点。ctypes 简单易用,适合简单的函数调用;cffi 功能强大,支持更复杂的用法;Cython 则提供了混合Python和C的编写能力,适用于性能要求较高的场景。

希望本文能帮助你在Python中更高效地调用C代码。如果你有任何问题或建议,欢迎在评论区留言交流!

参考链接

祝你在编程的道路上越走越远,技术越来越牛!🚀