Cython 中的类型转换

简介: Cython 中的类型转换

C 和 Python 在数据类型上都有各自的成熟特性,比如支持数据在不同类型之间进行转换。

# Py_ssize_t 是 ssize_t 的别名
cdef Py_ssize_t num = 123
# 声明一个指针类型的变量
cdef Py_ssize_t *p = &num
# 转成浮点型指针
cdef double *p2 = <double *> p

指针类型可以任意转换,这里我们将整型指针转成了浮点型指针,显然这是合法的。在 C 里面类型转换使用的是小括号,这里使用的是尖括号。

由于指针类型的转换不受限制,所以我们可以手动实现内置函数 id 的功能。

def my_id(obj):
    """
    实现内置函数 id 的功能
    我们知道 Python 的变量本质上就是个指针
    id(obj) 会获取 obj 指向对象的地址
    说白了,不就是 obj 本身吗?
    """
    # obj 是 PyObject * 类型, 转成 void *
    cdef void *p = <void *> obj
    # 而指针存储的值本质上是一串 16 进制整数
    # void * 再转成 Py_ssize_t 即可拿到地址
    return <Py_ssize_t> p

我们来测试一下:

import pyximport
pyximport.install(language_level=3)
import cython_test
num = 123
print(cython_test.my_id(num))
print(id(num))
"""
140706631063024
140706631063024
"""
s = "古明地觉"
print(cython_test.my_id(s))
print(id(s))
"""
2125601466576
2125601466576
"""
print(cython_test.my_id(object))
print(id(object))
"""
140706630839120
140706630839120
"""

我们实现的函数 my_id 和内置函数 id 打印的结果是一样的,所以 Python 虽然一切皆对象,但是我们操作的都是指向对象的指针,通过指针来操作对象。所以 id(obj) 表示的不是获取 obj 的地址,而是 obj 指向对象的地址,如果站在 C 的角度,那么就是 obj 存储的值本身。

所以 id 函数所做的事情就是把变量存储的地址转成 10 进制整数返回,我们如果想实现 id 函数的功能,只需要将变量(PyObject *)转成 void *,因为不同类型的指针可以相互转换,虽然转换之后指针的含义变了,但存储的地址不变。然后再将 void * 转成 Py_ssize_t,即可拿到存储的地址。

可能有人好奇,为什么先要转成 void * 之后,才能转成整型呢?直接转成整型不行吗?我们来测试一下,这两者的区别。

def my_id1(obj):
    # 先转成 void *,再转成 Py_ssize_t
    return <Py_ssize_t> <void *> obj
def my_id2(obj):
    # 直接转成 Py_ssize_t
    return <Py_ssize_t> obj
num = 666
print(my_id1(num))
print(my_id2(num))
"""
1617542740432
666
"""

区别很明显了,因为 Python 的变量是一个指向值的指针。如果转换之后的类型是指针类型,那么转换的是变量;如果转换后的型不是指针类型,那么转换的是对象。

所以 my_id2 返回的是 666,由于转换之后的类型不是指针类型,因此参与转换的是对象,相当于将 Python 整数转成了静态的 C 整数。并且 num 必须指向一个整数,否则它无法和 C 的 Py_ssize_t 类型相对应。

而对于 my_id1 函数,转换之后的类型是指针类型,所以参与转换的是变量、即 PyObject *。何指针都可以和 void * 转换,因此先将变量转成 void *,然后再由 void * 转成整型。

另外再补充一点,我们前面说指针的转换不受限制,针对的是纯 C 代码。但现在不是纯 C,而是 Cython,所以指针转换还是有限制的,这个限制主要针对 PyObject *,它只能转成 void *。

<int *> obj

上面的代码是有问题的,Python 类型的变量在指针转换的时候,只能转成 void *。当然啦,char * 是个例外。

name = b"satori"
print(<char *> name)  # b'satori'我们看到

char * 表示 C 的字符串类型,上述代码做的事情就是将 Python 字节串转成 C 字符串,当然打印的时候还是以 Python 字节串的形式打印的。所以 char * 算是一个例外吧,在 Cython 里面是把 char * 整体作为一个基础类型来看的,并且在转换的时候 name 必须指向一个 bytes 对象,否则会转换失败,因为 char * 和 Python 里面的 bytes 是相对应的。

但是我们发现,无论是指针类型、还是常规类型,这里都是 C 的类型。那么可不可以转成 Python 类型呢?答案是可以的,来看个例子。

def func(a):
    cdef list lst1 = list(a)
    print(lst1)
    print(type(lst1))
    cdef list lst2 = <list> a
    print(lst2)
    print(type(lst2))
func([1, 2, 3])
"""
[1, 2, 3]
<class 'list'>
[1, 2, 3]
<class 'list'>
"""
func((1, 2, 3))
"""
[1, 2, 3]
<class 'list'>
(1, 2, 3)
<class 'tuple'>
"""

打印的结果很明显,如果是 list(a),那么会根据 a 指向的对象创建一个新的列表,所以 lst1 一定指向一个列表。但 <list> a 则是将变量 a、也就是 PyObject * 拷贝一份,然后转成 PyListObject *,相当于将动态变量转成静态变量。

第一次调用 func,参数 a 指向了一个列表,但它是泛型指针。于是通过 <list> a 将它转成静态的,在操作的时候可以避免一些额外开销,当然不管是动态还是静态,指向的都是列表。另外这个例子有点刻意了,其实直接 cdef list lst2 = a 就可以了,因为做好了类型标注,那么会自动转换

然后第二次调用 func,参数 a 指向了一个元组,显然对于 list(a) 是没有影响的,因为它会创建新列表。但 <list> a 就有问题了,因为 a 实际指向的是元组,应该是 <tuple> a,所以转换失败。在早期的 Cython 中会引发一个SystemError,但目前不会了,如果转换失败还保留原来的类型。

可如果我们希望在无法转换的时候报错,这个时候要怎么做呢?

def func(a):
    # 将 <list> 换成 <list?> 即可
    cdef list lst2 = <list?> a
    print(lst2)
    print(type(lst2))

此时传递其它对象就会报错了,比如我们传递了一个元组,会报出 TypeError: Expected list, got tuple。

尖括号里面的类型可以任意,包括 C 类型以及 Python 内置类型。但说实话,使用尖括号做类型转换的场景不是很多,我们通过 cdef 指定类型时,会自动转换。当然后续,我们也会给出使用尖括号做类型转换的一些最佳实践。

相关文章
|
Python
Python数据类型与数据类型转换
Python数据类型与数据类型转换
40 0
|
2月前
|
数据处理 Python
Python 中的类型转换
【8月更文挑战第29天】
21 3
|
2月前
|
存储 程序员 Python
深入探讨Python中的变量和类型转换
【8月更文挑战第20天】
32 0
|
2月前
|
SQL 分布式计算 算法
【python】python指南(十):静态类型注解之Union
【python】python指南(十):静态类型注解之Union
30 0
|
3月前
|
索引 Python
python操作符或函数与数据类型不兼容
【7月更文挑战第11天】
35 1
|
3月前
|
机器学习/深度学习 编译器 测试技术
什么是 Python 编译器
**Python 编程语言以解释型为主,但也有编译器用于提升性能。CPython是默认解释器,先转为字节码再解释执行。PyPy是JIT编译器,执行速度快。Numba是针对数值计算的JIT编译器,优化数学运算。选择Python编译器要考虑性能、兼容性、内存使用及社区支持。对于机器学习,需支持科学库和GPU加速。**
|
5月前
|
Python 容器
Python数据类型转换
Python数据类型转换
|
5月前
|
存储 程序员 Python
Python 数据类型转换详解
Python 数据类型转换详解
42 0
|
5月前
|
安全 Python
Python-类型转换
Python-类型转换
51 3
|
5月前
|
Python
02-python的基础语法-01python字面量/注释/数据类型/数据类型转换
02-python的基础语法-01python字面量/注释/数据类型/数据类型转换