Cython 的融合类型

简介: Cython 的融合类型

Cython 将静态类型系统引入到了 Python 中,实现了基于类型的优化。但问题来了, 如果一个变量可能是不同的类型,该怎么办呢?比如一个变量既可能是整型,也可能是浮点型。

而 Cython 也考虑到了这一点,就是下面要介绍的融合类型。说融合类型可能让人感到陌生,如果说泛型是不是就很熟悉了。

Cython 目前提供了三种我们可以直接使用的融合类型,integral、floating、numeric,含义如下:

  • integral:等价于 C 的 short, int, long;
  • floating:等价于 C 的 float, double;
  • numeric:最通用的类型,包含上面的 integral、floating 以及复数;


当然这三个融合类型无法直接用,需要通过 cimport 导入。

from cython cimport integral
cpdef integral integral_max(integral a, integral b):
    return a if a >= b else b

上面这段代码,Cython 将会创建三个版本的函数:1)参数 a 和 b 都是 short 类型;2)参数 a 和 b 都是 int 类型;3)参数 a 和 b 都是 long 类型。

在调用的时候可以显式指定类型,否则会选择范围最大的类型,举个例子:

print(integral_max(<short> 1, <short> 2))
print(integral_max(<int> 1, <int> 2))
print(integral_max(<long> 1, <long> 2))

如果一个融合类型声明了多个参数,那么这些参数的类型都必须是融合类型中的同一种,所以下面的调用都是不合法的。

print(integral_max(<short> 1, <int> 2))
print(integral_max(<int> 1, <long> 2))
print(integral_max(<long> 1, <short> 2))

融合类型相当于多个类型的组合,比如 integral 是 short, int, long 的组合,至于 integral 最终会表现出哪一种,则取决于传递的参数。但融合类型在同一时刻,只能表示一种类型,什么意思呢?比如我们上面的参数 a 和 b 的类型是相同的,都是 integral 类型,那么最终 a 和 b 要么都是 short、要么都是 int、要么都是 long,不存在 a 是 int、b 是 short 这种情况。

当然这背后的原理我们也说了,如果出现了融合类型,那么 Cython 会根据融合类型里面的每一个类型都单独创建一个函数。在调用时,根据传递参数类型,来判断调用哪一个版本的函数。

到目前为止,总共出现了三个融合类型,都需要从 cython 这个名字空间里面 cimport 之后才能使用。那么问题来了,我们能不能自己创建融合类型呢?答案是可以的。

ctypedef fused list_tuple:
    list
    tuple
# a 和 b 要么都为列表、要么都为元组
# 但不可以一个是列表、一个是元组
cpdef list_tuple func(list_tuple a, list_tuple b):
    return a + b
# Cython 会根据我们传递的参数来判断,调用哪一种函数
print(
    func([1, 2], [3, 4])
)  # [1, 2, 3, 4]
# 我们也可以显式指定要调用的函数版本
print(
    func[list]([11, 22], [33, 44])
)  # [11, 22, 33, 44]
print(
    func[tuple]((111, 222), (333, 444))
)  # (111, 222, 333, 444)

还是挺简单的,并且组合成融合类型的多个类型,可以是 C 的类型,也可是 Python 的类型。

另外再次强调,list_tuple 虽然既可以是 list,也可以是 tuple,但是在同一个函数中只能表现出一种类型。如果我们给 a 传递 list、给 b 传递 tuple,看看会有什么结果。

import pyximport
pyximport.install(language_level=3)
import cython_test
try:
    cython_test.func([], ())
except TypeError as e:
    print(e)
"""
Argument 'b' has incorrect type (expected list, got tuple)
"""
# 当 a 接收的是一个列表时
# 那么就可以将 list_tuple 看成是 list 了
# 于是 b 也必须接收一个列表
try:
    cython_test.func((), [])
except TypeError as e:
    print(e)
"""
Argument 'b' has incorrect type (expected tuple, got list)
"""
# 当 a 接收的是一个元组时
# 那么就可以将 list_tuple 看成是 tuple 了
# 于是 b 也必须接收一个元组

另外我们上面只出现了一种融合类型,我们还可以定义多种。

ctypedef fused list_tuple:
    list
    tuple
ctypedef fused dict_set:
    dict
    set
# 会生成如下四种版本的函数:
# 1) 参数 a、c 为列表,b、d 为字典
# 2) 参数 a、c 为列表,b、d 为集合
# 3) 参数 a、c 为元组,b、d 为字典
# 4) 参数 a、c 为元组,b、d 为集合
cdef func(list_tuple a,
          dict_set b,
          list_tuple c,
          dict_set d):
    print(a, b, c, d)
# 会根据我们传递参数来判断选择哪一个版本的函数
func([1], {"x": ""}, [], {})
"""
[1] {'x': ''} [] {}
"""
# 依旧可以显式指定类型,不让 Cython 帮我们判断
# 但由于存在多种混合类型
# 一旦指定、那么每一个混合类型都要指定
func[list, dict]([1], {"x": ""}, [], {})
"""
[1] {'x': ''} [] {}
"""

此外,我们必须写成 func[list, dict] 这种形式,不可以是 func[dict, list]因为类型为 list_tuple 的参数先出现,类型为 dict_set 的参数后出现。所以中括号里面第一个出现的类型一定是 list_tuple 里面的类型(list 或 tuple),第二个是 dict_set 里面的类型(dict 或 set)。

因此一旦指定版本,那么只能是以下四种之一:

  • func[list, dict](...)
  • func[list, set](...)
  • func[tuple, dict](...)
  • func[tuple, set](...)


当然啦,别忘记在传参的时候务必保证参数类型正确。

多说一句题外话,如果你用过 Go 的话,你会发现 Go 的泛型和 Cython 的融合类型非常相似,我们举个栗子。

Go 泛型:

Cython 融合类型:

对比一下之后,是不是发现两者非常像呢?但很明显,Cython 的融合类型、或者也叫泛型,在设计上要更优秀一些。比如定义完 T 之后,直接使用 T 即可;而 Go 里面在定义完 T 之后还不能直接用,必须要再起一个名字(T1),然后用这个新起的名字。

好了,言归正传,在定义函数时,不仅仅只有融合类型,还可以有具体的类型,举个例子:

ctypedef fused list_tuple:
    list
    tuple
ctypedef fused dict_set:
    dict
    set
# 里面除了融合类型之外,还有一个 int 类型
cdef func(list_tuple a, 
          dict_set b, 
          int xxx, 
          list_tuple c, 
          dict_set d):
    print(a, b, c, d, xxx)
# 显然调用是无影响的,因为在 func 后面的 [ ] 里面
# 只需要指定融合类型对应的具体类型,其它的不需要管
func[list, dict]([1], {"x": ""}, 123, [], {}) 
func[list, set]([1], {1, 2, 3}, 456, [], {2})

最后,上面的 func 函数还有一种调用方式,我们来看一下:

cdef func(list_tuple a, 
          dict_set b, 
          int xxx, 
          list_tuple c, 
          dict_set d):
    print(a, b, c, d, xxx)
# 声明一个函数指针,指向的函数接收五个参数
# 类型分别是 list, set, int, list, set,返回 object
# 此时必须将所有参数的类型全部指定,不能只指定融合类型
# 并且声明为同一种融合类型的参数的具体类型仍然要一致
cdef object (*func_with_list_set)(list, set, int, list, set)
# 赋值
func_with_list_set = func
func([], {1}, 123, [], {2})
"""
[] {1} [] {2} 123
"""
# 或者这种方式也是可以的
# 将 func 转成 <object (*)(list, set, int, list, set)>
# 相当于将函数指针转成了接收五个参数、返回一个object类型的指针
(<object (*)(list, set, int, list, set)> func)([], {1}, 123, [], {2})
"""
[] {1} [] {2} 123
"""
# 还有就是之前的方式,只不过可以拆开使用
# [] 里面只需要指定融合类型
cdef func_with_tuple_dict = func[tuple, dict]
func_with_tuple_dict((1, 2), {"a": "b"}, 456, (11, 22), {"b": "a"}) 
"""
(1, 2) {'a': 'b'} (11, 22) {'b': 'a'} 456
"""

到此,关于融合类型的创建和用法我们就说完了,总之融合类型不仅可以用在函数的参数和返回值中,也可以用于普通的变量声明。

但是变量到底是融合类型的哪一种,还需要我们动态判断。

ctypedef fused list_tuple_dict:
    list
    tuple
    dict
# 在判断的时候,可以对 val 进行判断
# 比如使用 type 或者 isinstance
# 但是我们还可以对融合类型本身判断
cpdef func(list_tuple_dict val):
    """
    Cython 会根据该函数生成以下三个函数
    cdef func(list val)
    cdef func(tuple val)
    cdef func(dict val)
    
    根据 val 类型的不同,调用不同版本的函数
    所以不管最终调用的是哪一个版本的函数
    类型都是确定的
    """
    # 因此在编写代码的时候
    # 根据融合类型本身就可以判断
    if list_tuple_dict is list:
        print("val 是 list 类型")
    elif list_tuple_dict is tuple:
        print("val 是 tuple 类型")
    else:
        print("val 是 dict 类型")

然后我们调用一下试试:

import pyximport
pyximport.install(language_level=3)
import cython_test
cython_test.func([])
cython_test.func(())
cython_test.func({})
"""
val 是 list 类型
val 是 tuple 类型
val 是 dict 类型
"""
# 如果类型不是融合类型中的任意一种
# 那么就会报错
try:
    cython_test.func(123)
except TypeError as e:
    print(e)
"""
No matching signature found
"""

混合类型具体会是哪一种类型,在参数传递的时候便会得到确定。

因此 Cython 中的泛型编程还是很强大的,但是在工作中的使用频率其实并不是那么频繁。

相关文章
|
12月前
|
Python
Python基础分享之面向对象的进一步拓展
Python基础分享之面向对象的进一步拓展
|
2天前
|
存储 缓存 API
比较一下 Python、C、C 扩展、Cython 之间的差异
比较一下 Python、C、C 扩展、Cython 之间的差异
6 0
|
2月前
|
数据采集 机器学习/深度学习 Java
Python中的偏函数及其广泛应用方式
Python 中的 functools.partial 函数不仅仅是一种实用工具,更是贯穿于各类编程场景的核心构件。 无论是在函数式编程、装饰器设计、GUI 编程、Web 开发、异步任务处理,还是数据预处理和机器学习等领域,偏函数都能助力开发者简化代码结构、增强代码可读性和可维护性,进而提升整体编程效率。 通过灵活运用偏函数,我们可以更好地封装和复用代码逻辑,打造出更为优雅、高效的程序。
|
4月前
|
Python
无缝融合:使用 Python 和 PyFFmpeg 合并视频的完整指南
使用Python和PyFFmpeg合并视频教程:安装pyffmpeg和subprocess模块,编写merge_videos函数,通过ffmpeg命令行工具进行视频拼接。运行脚本将多个.mp4文件合并为一个,并保存为merged_video.mp4。简单易用,提升内容创作效率。
160 2
|
3月前
|
设计模式 Python
深度揭秘!Python元类:掌握它,让你的代码拥有创造类的能力
【7月更文挑战第6天】Python元类探秘:**元类是类的类,用于控制类的创建。通过定义元类,可自定义类的行为,如动态添加方法或改变继承结构。示例中,`my_metaclass`在创建类时添加`new_method`。元类强大且适用于高级编程,如动态修改、注册类或实现设计模式。理解并善用元类能提升Python编程技巧。
33 0
|
5月前
|
安全 Serverless 数据处理
通用函数(ufuncs)在NumPy中的应用实践
【4月更文挑战第17天】通用函数(ufuncs)是NumPy中非常重要的工具,它们允许对数组中的每个元素执行相同的数学运算,无需编写循环。通过ufuncs,我们可以高效地处理大规模数据集,并利用广播机制在形状不同的数组之间进行运算。掌握ufuncs的应用实践,将极大地提升我们在数值计算和数据处理方面的效率。
|
5月前
|
存储 Python
【Python基础】字典和函数拓展
【Python基础】字典和函数拓展
|
10月前
|
TensorFlow 算法框架/工具 Python
把python函数转化为 tensorflow 函数 加速运算
把python函数转化为 tensorflow 函数 加速运算
34 1
|
5月前
|
人工智能 测试技术 Python
软件测试/人工智能|Python 数据类型转换解析:理解数据之间的灵活转换
软件测试/人工智能|Python 数据类型转换解析:理解数据之间的灵活转换
51 0
|
缓存 并行计算 PyTorch
终于可用可组合函数转换库!PyTorch 1.11发布,弥补JAX短板,支持Python 3.10
终于可用可组合函数转换库!PyTorch 1.11发布,弥补JAX短板,支持Python 3.10
399 0
终于可用可组合函数转换库!PyTorch 1.11发布,弥补JAX短板,支持Python 3.10