Skip to main content

调用C

info

Python社区的发展依赖于开放、透明的决策过程。每个PEP都经过充分讨论,这种民主化的开发方式保证了Python的长期健康发展。

当你遇到"为什么Python要这样设计"的问题时,PEP往往能给你答案。

PEP 1 – PEP目的和指南

Python 调用 C

Python 的底层是 C 写的(实际上大部分高级编程语言都是 C 写的)因此Python可以调用以下C/C++文件类型:

  • C源代码文件(.c)
  • C++源代码文件(.cpp、.cxx、.cc)
  • 编译后的共享库(Linux/Unix的.so、Windows的.dll、macOS的.dylib)
  • 编译后的静态库(Linux/Unix的.a、Windows的.lib)

因此互相调用的逻辑主要是:数据类型转换、编译库的链接、接收返回值。

python+c/c++混合编程如:

原生的 Python.h

cython

pybind11:pytorch 也采用该方法

ctypes、cffi、SWIG、Boost.Pytho 等

但不论是哪个方法,大致的流程都是:转换数据类型->编译代码->生成编译后的文件(.pyd .pyc .pyo .so .dll 等)

冷知识:

python的import不止能导入.py后缀结尾的文件

pyc是由py文件经过编译后生成的二进制文件,py文件变成pyc文件后,加载的速度有所提高,并且可以实现源码隐藏。

pyo是优化编译后的程序,也可以提高加载速度,针对嵌入式系统,把需要的模块编译成pyo文件可以减少容量。

.so和.dll分别是Linux和window的动态库

这些都可以被import导入,所以我们只需要编译C代码,然后import导入即可。

代码编写

一个求某个数可以分解为多少个质数之和代码,其中最核心的代码是判断一个数是否为质数。我们使用C语言实现这个被频繁调用的功能。

prime.c
#include <math.h>

int is_prime(int n) {
if (n < 2) return 0;
for (int i = 2; i <= (int)sqrt(n); i++) {
if (n % i == 0) return 0;
}
return 1;
}

编译:gcc -shared -o prime.so prime.c

import ctypes
import timeit

# 加载C动态库
prime_lib = ctypes.CDLL('./prime.so')
is_prime = prime_lib.is_prime
is_prime.argtypes = [ctypes.c_int]
is_prime.restype = ctypes.c_int

def count_prime_pairs(n):
count = 0
for i in range(2, n // 2 + 1):
if is_prime(i) and is_prime(n - i):
count += 1
return count

def is_prime_py(n):
if n < 2:
return False
for i in range(2, int(n ** 0.5) + 1):
if n % i == 0:
return False
return True

def count_prime_pairs_py(n):
count = 0
for i in range(2, n // 2 + 1):
if is_prime_py(i) and is_prime_py(n - i):
count += 1
return count

if __name__ == "__main__":
n = int(input("输入一个正整数: "))
print("C混合版结果:", count_prime_pairs(n))
print("纯Python结果:", count_prime_pairs_py(n))

# 性能对比
py_time = timeit.timeit(lambda: count_prime_pairs_py(n), number=100)
c_time = timeit.timeit(lambda: count_prime_pairs(n), number=100)
print(f"Pure Python: {py_time:.4f} seconds")
print(f"Python+C: {c_time:.4f} seconds")