在 Cython 中声明结构体、共同体、枚举
楔子
C 语言里面存在结构体、共同体、枚举,而这些在 Cython 里面也是支持的,只不过声明的方式不太一样,下面来看一看。
结构体
在 C 里面定义一个结构体,一般有两种方式。
#include <stdio.h>// 直接定义,此时 struct Girl 整体是一个类型
struct Girl {
char *name;
int age;
};
// 使用 typedef 起一个别名
// 此时 Boy 是一个类型
typedef struct {
char *name;
int age;
} Boy;
intmain() {
// 声明一个 struct Girl 类型的实例
struct Girl g;
g.name = "mercy";
g.age = 37;
// 声明一个 Body 类型的实例
Boy b;
b.name = "hanzo";
b.age = 38;
printf("name = %s, age = %d\n", g.name, g.age);
printf("name = %s, age = %d\n", b.name, b.age);
/*
name = mercy, age = 37
name = hanzo, age = 38
*/
}
以上是 C 的结构体,在 Cython 里面要如何定义呢?
# 相当于 C 的 struct Girl{...};
cdef struct Girl:
char *name
int age# 相当于 C 的 typedef struct {...} Boy;
ctypedef
struct Boy:
char*name
intage# 创建结构体实例,无论结构体使用哪一种方式定义
# 在创建实例的时候,格式都是一样的
cdef Girl g1
cdef Boy b1
# 创建的时候也可以直接赋值,支持位置参数和关键字参数
# 但是使用关键字参数要注意顺序
# 结构体字段出现的顺序,就是参数的顺序
cdef Girl g2 = Girl("mercy", 37)
cdef Boy b2 = Boy("hanzo", age=38)
# 打印的时候会以字典的形式打印
print(g2)
print(b2)
"""
{'name': b'mercy', 'age': 37}
{'name': b'hanzo', 'age': 38}
"""
# 当然啦,也可以先声明,然后单独赋值
# 我们上面创建了 g1 和 b1,下面赋值
g1.name, g1.age = "mercy", 37
b1.name, b1.age = "hanzo", 38
print(g1)
print(b1)
"""
{'name': b'mercy', 'age': 37}
{'name': b'hanzo', 'age': 38}
"""
# 通过 Python 的字典赋值
# 显然它涉及数据转换,会有额外开销
# 不建议使用此方式
cdef Girl g3 = {"name": "mercy", "age": 37}
cdef Boy b3 = {"name": "hanzo", "age": 38}
print(g3)
print(b3)
"""
{'name': b'mercy', 'age': 37}
{'name': b'hanzo', 'age': 38}
"""
在 C 里面还存在结构体的嵌套:
struct Girl {
struct{
char *name;
int age;
} Person;
int length;
};
Cython 也是允许结构体嵌套的,但是定义必须要单独拿出来,什么意思呢?看个例子就明白了。
cdefstruct Person:
char *name
int age# Person 的定义必须要单独拿出来定义
# 不可以嵌套定义
cdefstruct Girl:
Person person
int length
cdefGirl g = Girl(person=Person("mercy", 37), length=167)
print(g)
"""
{'person': {'name': b'mercy', 'age': 37}, 'length': 167}
"""
另外,当定义结构体的时候,字段的类型必须都是 C 的类型。
共同体
共同体的声明在 C 和 Cython 里面都和结构体类似,我们来看一下。
cdef union U1:
short n1
int n2ctypedef union U2:
short n1
int n2
cdef U1 u1
u1.n2 = 0X1234_4321
print(u1.n1 == 0X4321) # True
cdef U2 u2
u2.n2 = 0X4321_1234
print(u2.n1 == 0X1234) # True
使用方法和结构体类似,关于共同体的具体知识这里就不多说了,可以查询 C 语言共同体相关的内容。总之它的目的是为了节省内存,一个结构体实例所占的内存,等于内部所有字段所占内存之和;而一个共同体实例所占的内存,等于内部占用内存最大的字段。
cdefstructS:
shortfield1 # 2 字节
intfield2 # 4 字节
ssize_tfield3 # 8 字节cdefunionU:
short field1
int field2
ssize_t field3
cdefS s
cdefU u
# 等于所有字段所占内存之和
# 2 + 4 + 8 = 14,但由于存在内存对齐
# 所以是 16
print(sizeof(s)) # 16
# 最长字段所占的内存
# 因此是 8
print(sizeof(u)) # 8
因此共同体明显要省内存,但是修改某一个字段,会影响其它字段,因为字段之间共用一组内存。而结构体则不会,字段之间互不影响,因为它们使用的是不同的内存。
枚举
定义枚举也很简单,我们可以在多行中定义,也可以在单行中定义然后用逗号隔开。
# 相当于 C 的 enum COLOR1 {};
cdef enumCOLOR1:
RED = 1
YELLOW = 3
GREEN = 5# 相当于 C 的 typedef enum {} COLOR2;
ctypedefenumCOLOR2:
PURPLE, BROWN
# 在 C 里面声明一个枚举变量
# 可以是 enum COLOR1,整体是一个类型
# 也可以直接使用 COLOR2
# 但在 Cython 里面声明方式是一样的
# cdef COLOR1、cdef COLOR2
cdefCOLOR1 c1 = YELLOW
cdefCOLOR2 c2 = BROWN
print(YELLOW)
print(BROWN)
"""
3
1
"""
枚举比较简单。
以上就是结构体、共同体和枚举在 Cython 中的使用方式,但说实话,如果只是写 Cython,那么我们很少会用到这些 C 级结构。像结构体、共同体、枚举这些结构,一般都是在和外部的 C 代码交互的时候才会用到,比如我们要调用一个现有的 C 库函数,但这个函数接收一个结构体,那么这个时候我们会构造一个结构体然后传过去。
使用 ctypedef 给类型起别名
在 C 语言中有一个 typedef 关键字,可以用来给类型起别名,就像我们上面演示的那样。如果是使用 struct S {}; 这种方式定义结构体,那么 struct S 整体是一个类型。但我们可以通过 typedef 起一个别名,比如 typedef struct S{} S2; 那么声明的时候既可以使用 struct S,也可以直接使用 S2,因为 typedef 给 struct S 起了一个别名叫 S2。
注意:typedef 并不是定义了一个新的类型,它只是给一个已经存在的类型起了一个别名而已,像 CPython 里面有一个 Py_ssize_t,它就是 ssize_t 的别名。所以这个关键字的名字起的不好,会让人以为使用 typedef 是定义一个新类型一样。
#include <stdio.h>typedef int MY_INT;
intmain() {
MY_INT age = 17;
printf("%d\n", age); // 17
}
我们上面给 int 起了一个别名叫 MY_INT,而在 Cython 里面也是支持的,只不过关键字不叫 typedef,而是叫 ctypedef。
ctypedeflistMY_LISTdef foo(MY_LIST lst):
pass
文件名叫 cython_test.pyx,我们调用一下看看。
import pyximport
pyximport.install(language_level=3)import cython_test
try:
cython_test.foo(123)
except TypeError as e:
print(e)
"""
Argument 'lst' has incorrect type (expected list, got int)
"""
参数接收的是 list 类型,因为 MY_LIST 是 list 的别名,但是我们传了一个整数进去,所以报错了。当然不管什么 Python 类型,None 都是满足的。
ctypedef 可以作用于 C 的类型也可以作用于 Python 类型,起别名之后,这个别名可以像上面那样作用于函数参数,也可以用于声明一个变量,比如 cdef MY_LIST lst。
但是不可以像这样:MY_LIST("123"),或者 isinstance(xxx, MY_LIST)。起的别名只能用在 C 语义当中,比如类型声明、或者 <MY_LIST?> 将变量静态化。但是不能用在 Python 语义当中,比如调用之类的,否则会报错:'MY_LIST' is not a constant, variable or function identifier。
ctypedef 一般也是用在和外部 C 代码交互上面,如果是纯 Cython,那么很少使用 ctypedef 起别名。但是对于 C++ 来说起别名则很常见,因为使用 typedef 可以显著的缩短长模板类型。
另外 ctypedef 对出现的位置也是有要求的,如果不和外部 C 代码交互的话,它应该出现在全局作用域中,不可以出现在函数等局部作用域里,也不可以出现在 if, for, while 语句块内。