放弃 Julia 之后,我开始想要用 Python + ctypes 满足自己的需求。由于 Windows 搞这个很麻烦,就在 Ubuntu 上搞了,不过我是在 WSL 上做的哈哈。

Python 调用 C 示例

ctypes 官方中文文档中选择自己 Python 对应版本的文档。有很清晰的描述,然后在 Pythonlab.com 中很直观的例子:

1
2
3
4
5
6
7
8
9
10
// add.c
int add_int(int num1, int num2){
return num1 + num2;
}

// add.cpp
extern "C" int add_int(int num1,int num2); // 这样就不用改之前的代码了!
int add_int(int num1, int num2){
return num1 + num2;
}

由于 C++ 支持函数重载功能,在编译时会更改函数名。

所以在函数声明时,前缀 extern "C" 则确保按 C 的方式编译。

然后把上面 add.c/add.cpp 编译成 .so 文件:

1
2
3
#For Linux
$ gcc -fPIC -shared -o libadder.so add.c
$ g++ -fPIC -shared -o libadder.so add.cpp

再调用就好了

1
2
3
4
5
6
7
8
from ctypes import *

#load the shared object file
adder = CDLL('./libadder.so')

#Find sum of integers
res_int = adder.add_int(4,5)
print("Sum of 4 and 5 = ", res_int)

Python 调用自己写的 primepi C++ 函数

Python 调用自己写的 PrimePI 岂不美哉

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
typedef long long LL;
const int N = 1e7 + 2;
int p[N], pi[N];
bool isp[N];

#include<math.h>
// 仅对要用的做extern操作
extern "C" {
void init();
LL primepi(LL x);
}

int initprime() {
int cnt = 1;
p[1] = 2;
isp[2] = true;
for (int i = 3; i < N; i += 2) isp[i] = true;
for (int i = 3; i < N; i += 2) {
if (isp[i]) p[++cnt] = i;
for (int j = 2, t = (N - 1) / i + 1; j <= cnt && p[j] < t; ++j) {
isp[i * p[j]] = false;
if (i % p[j] == 0) break;
}
}
return cnt;
}
const int M = 7;
const int PM = 2 * 3 * 5 * 7 * 11 * 13 * 17;
int phi[PM + 1][M + 1], sz[M + 1];
void init() {
initprime();
pi[2] = 1;
for (int i = 3; i < N; ++i) {
if (isp[i]) pi[i] = pi[i - 1] + 1;
else pi[i] = pi[i - 1];
}
sz[0] = 1;
for (int i = 0; i <= PM; ++i) phi[i][0] = i;
for (int i = 1; i <= M; ++i) {
sz[i] = p[i] * sz[i - 1];
for (int j = 1; j <= PM; ++j) {
phi[j][i] = phi[j][i - 1] - phi[j / p[i]][i - 1];
}
}
}
LL primepi(LL x);
LL primephi(LL x, int s) {
if (s <= M) return phi[x % sz[s]][s] + (x / sz[s]) * phi[sz[s]][s];
if (x / p[s] <= p[s]) return primepi(x) - s + 1;
if (x < N && x / p[s] / p[s] <= p[s]) {
int s2x = pi[(int)(sqrt(x + 0.2))];
LL ans = pi[x] - LL(s2x + s - 2) * (s2x - s + 1) / 2;
for (int i = s + 1; i <= s2x; ++i) {
ans += pi[x / p[i]];
}
return ans;
}
return primephi(x, s - 1) - primephi(x / p[s], s - 1);
}
LL primepi(LL x) {
if (x < N) return pi[x];
int ps2x = pi[int(sqrt(x + 0.2))];
int ps3x = pi[int(cbrt(x + 0.2))];
LL ans = primephi(x, ps3x) + LL(ps2x + ps3x - 2) * (ps2x - ps3x + 1) / 2;
for (int i = ps3x + 1, ed = ps2x; i <= ed; ++i) {
ans -= primepi(x / p[i]);
}
return ans;
}

执行 g++ -fPIC -shared -o libprimepi.so primepi.cpp 后运行下面 Python 程序

1
2
3
4
5
6
# test.py
from ctypes import *
c = CDLL('./libprimepi.so')
c.init()
# 再大就崩了,Segmentation fault (core dumped)
print(c.primepi(9876543210))

C 中用 Python 老是提醒没有 Python.h,试了网上的方法,各种系统都不行服了。不过无所谓最在 C 中用 Python 啊

SageMath 调用 C 的方法

动态库和静态库

Make

既然要混合编程了,哪必然要涉及到 makefile 了,于是我去 知乎

示例源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// main.c
int main() {
printf("hello world\n");
fun1();
fun2();
}

// fun1.c
void fun1() {
printf("this is fun1\n");
}

// fun2.c
void fun2() {
printf("this is fun2\n");
}

示例 Makefile(注意 Makefile 一定要 Tab 缩进)

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
26
27
28
29
30
31
32
33
34
35
# makefile1
app: main.c fun1.c fun2.c
gcc main.c fun1.c fun2.c -o app

# makefile2
app: main.o fun1.o fun2.o
gcc main.o fun1.o fun2.o -o app
main.o: main.c
gcc -c main.c -o main.o
fun1.o: fun1.c
gcc -c fun1.c -o fun1.o
fun2.o: fun2.c
gcc -c fun2.c -o fun2.o

# makefile3
obj = main.o fun1.o fun2.o
target = app
CC = gcc
$(target): $(obj)
$(CC) $(obj) -o $(target)
%.o: %.c
$(CC) -c $< -o $@

# makefile
src = $(wildcard ./*.c)
obj = $(patsubst %.c, %.o, $(src)) # obj = $(src:%.c=%.o)
target = app
CC = gcc
$(target): $(obj)
$(CC) $(obj) -o $(target)
%.o: %.c
$(CC) -c $< -o $@
.PHONY: clean
clean:
rm -rf $(obj) $(target)

其中 $ 自然是取值操作 % 是未定元的感觉,然后 wildcard, patsubst 从版本中就能看出。

$<:第一个依赖文件;$@:目标文件;$^:所有不重复的依赖文件,以空格分开。

依次执行 make -f makefilei 即可,最后 make (-f makefile 可省略), make clean 是最终版本

Make 规则

1
2
目标: 预置条件
<TAB> 步骤

这里写的很详细