详解历时五年的 Cython3.0 都发生了哪些变化(一)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: 详解历时五年的 Cython3.0 都发生了哪些变化(一)


楔子




关于 Cython,我们目前已经详细介绍了它的语法以及使用方式,不过版本是 0.29。而前一段时间,Cython 发布了 3.0 版本,那么它和 0.29 版本相比都发生了哪些变化呢?

本次就来聊一聊其中的一些比较大的变化。


变量支持非 ASCII 字符




在 0.29 以及之前的版本中,Cython 要求变量名必须是 ASCII 字符,但在 3.0 版本中,这一限制被取消了。

先来看看 0.29 版本。

# 文件名:cython_test.pyx
cpdef str 你好世界():
    return "Hello World"

如果我们尝试编译这个文件,那么会报错。

a1c862f1a356a14f74ef8c986209843b.png

告诉我们标识符不合法,但如果使用 Cython3.0 编译的话,那么结果没有任何问题。

使用 3.0 版本。

import pyximport
pyximport.install(language_level=3)
import cython_test
print(cython_test.你好世界())  # Hello World

可以看到使用 3.0 版本的 Cython 没有任何问题,所以 0.29 和 3.0 之间的一个区别就是 3.0 支持使用非 ASCII 字符(比如中文)定义变量。

但说实话,这个功能没太大用,因为在开发中也基本不会用中文给变量命名。


开启 generator_stop




对于生成器而言,return 的本质就是向外抛出一个 StopIteration 异常,代表这个生成器结束了。

def gen():
    yield 1
    yield 2
    yield 3
    return "结束啦"
g = gen()
print(g.__next__())  # 1
print(g.__next__())  # 2
print(g.__next__())  # 3
try:
    g.__next__()
except StopIteration as e:
    print(e.value)  # 结束啦

但在 Python 3.7 版本之前,我们也可以手动引发一个 StopIteration。

def gen1():
    yield 1
    yield 2
    yield 3
    return "结束啦"
def gen2():
    yield 1
    yield 2
    yield 3
    raise StopIteration("结束啦")

在 3.7 以前,上面的两个生成器函数是等价的,但这就产生了一个问题。举个例子:

def gen():
    yield 1
    yield 2
    # 只是单纯地抛出一个 StopIteration
    # 但在 Python 3.7 之前,它等价于 return "middle value"
    raise StopIteration("middle value")
    yield 3
    return "结束啦"

所以为了消除这样的误会,从 Python 3.7 开始,结束生成器一律通过 return。至于生成器内部引发的 StopIteration 则会被转化为 RuntimeError。

而在 Python 3.7 之前如果想开启这一功能,需要通过 __future__ 来实现。

from __future__ import generator_stop

然后重点来了,如果你使用的是 Cython 3.0,并且以 py3 的模式编译,那么即使解释器版本低于 3.7,这一功能默认也是开启的。

说实话,这个功能对我们来说,也没有什么用。


默认以 py3 的模式编译




在 Cython 0.29 的时候,默认是以 Python2 的语义编译 .pyx 的,如果希望以 Python3 的语义进行编译,那么需要将 language_level 参数指定为 3,否则就会抛出警告。

ccd19daf3c675dc88b619ea295da4113.png

但从 Cython 3.0 开始,默认则是以 Python3 的语义进行编译,如果希望兼容 Python2,那么需要显式地将该参数指定为 2。

除了极端情况,我们完全不需要兼容 Python2,因为会失去 Python3 的很多优秀特性。


__init_subclass__ 的问题




之前介绍过 __init_subclass__ 这个魔法方法,它在一些简单的场景下可以替代元类,举个例子。

class Base:
    def __init_subclass__(cls, **kwargs):
        """
        钩子函数,当该类被继承时会自动触发此函数
        注意:cls 不是当前的 Base,而是继承 Base 的类
        """
        for attr, val in kwargs.items():
            type.__setattr__(cls, attr, val)
            
class Girl(Base, name="古明地觉", address="地灵殿"):
    pass
print(Girl.name)  # 古明地觉
print(Girl.address)  # 地灵殿

需要注意的是,__init_subclass__ 这个函数是被 classmethod 隐式装饰的,当然我们也可以显式地装饰它。

class Base:
    @classmethod
    def __init_subclass__(cls, **kwargs):
        for attr, val in kwargs.items():
            type.__setattr__(cls, attr, val)

在 Python 代码中,上面两种做法都是可以的。但如果是在 Cython 里面,则必须要显式装饰,否则就会出现参数错误,因为 Cython 不会帮你隐式装饰。

下面举例说明:

# 文件名:cython_test.pyx
class Base:
    def __init_subclass__(cls, **kwargs):
        for attr, val in kwargs.items():
            type.__setattr__(cls, attr, val)

我们将 Base 类的定义移动到了 pyx 文件中,然后来导入它。

import pyximport
pyximport.install(language_level=3)
from cython_test import Base
try:
    class Girl(Base):
        pass
except TypeError as e:
    print(e)
"""
__init_subclass__() takes exactly 1 positional argument (0 given)
"""

告诉我们 __init_subclass__ 需要一个位置参数,如果想解决这一点,那么使用 classmethod 显式装饰一下即可。

但以上都是 Cython 0.29 版本以及之前才会出现的问题,如果是 Cython 3.0,那么表现和纯 Python 代码是一样的,也会隐式装饰。



类型注解延迟解析



先说一下类型注解,Python 从 3.5 开始支持类型注解。

class A:
    pass
def foo(a: A, b: str, c: int):
    pass
print(foo.__annotations__)
"""
{'a': <class '__main__.A'>, 'b': <class 'str'>, 'c': <class 'int'>}
"""

像 FastAPI、Pydantic 等框架都高度依赖 Python 的类型注解功能,然后需要注意的是,类型注解在定义函数的时候就被解析了,所以下面这种做法就会出问题:

class A:
    @classmethod
    def create_instance(cls) -> A:
        pass
"""
NameError: name 'A' is not defined
"""

定义类 A,它内部有一个 create_instance 类方法,通过类型注解表示该方法会返回一个 A 的实例对象。但这个类在定义的时候却报错了,原因就是 Python 在解析的时候,A 这个类还没有来得及创建。

所以从 Python3.7 开始,便又引入了类型注解延迟解析:

# 3.7 开始支持类型注解延迟解析,但必须导入 annotations
# 而 3.10 开始则不再需要,会变成默认行为
from __future__ import annotations
class A:
    # 解释器在解析 foo 的时候,B 还没有定义
    # 不过没有关系,因为类型注解会被延迟解析
    def foo(self, b: B):
        pass
class B:
    pass
# 启用延迟类型注解后,Python 会把类型提示存储为字符串
# 所以 value 不再是 <class '__main__.B'>,而是字符串 "B"
print(A.foo.__annotations__)  # {'b': 'B'}
# 当调用 typing.get_type_hints() 时才进行解析
import typing
print(typing.get_type_hints(A.foo))  # {'b': <class '__main__.B'>}

如果你不想使用 __future__ 的话,那么也可以换一种方式。

class A:
    def foo(self, b: "B"):
        pass
class B:
    pass
print(A.foo.__annotations__)  # {'b': 'B'}
import typing
print(typing.get_type_hints(A.foo))  # {'b': <class '__main__.B'>}

在声明的时候直接指定为字符串即可,这样即便 Python 版本低于 3.10,也是可以的。

然后类型注解在 Cython 中也是支持的,只不过在 Cython 中我们更习惯使用 C 风格定义变量。

# 在定义 C 级变量的时候,必须使用 C 风格进行变量声明
cdef str name = "古明地恋"
# 注意:不可以写成 cdef name: str = "古明地恋",这是错误的语法
# 但在函数中是可以的
# `类型 变量` 属于 C 风格,比如 list data
# `变量: 类型` 属于 Python 风格,比如 target: int
cpdef Py_ssize_t search(list data, target: int):
    if target in data:
        return data.index(target)
    return -1

然后是返回值的问题,如果使用 cdef、cpdef 定义函数,那么返回值类型声明必须写在 cdef、cpdef 后面。

cpdef list foo():
    return [1, 2, 3, 4, 5]
# 但 cpdef foo() -> list: 这么做是不合法的,会编译错误
"""
Return type annotation is not allowed in cdef/cpdef signatures
"""
# 事实上 cdef 和 cpdef 后面如果不指定类型,那么默认是 object
# 这种做法只能用在 def 定义的函数中
def bar() -> tuple:
    return 1, 2, 3

最后在 Cython 中同样也支持延迟类型注解。

# 文件名: cython_test.pyx
class A:
    def foo(self, b: "B"):
        pass
class B:
    pass

如果是 Cython 0.29,那么必须写成 "B",否则会出现 NameError。但从 Cython 3.0 开始,即使不写成字符串的形式,也没有任何问题。

但说实话,在 Cython 中声明变量,最好使用 C 风格的方式。也就是类型 变量的方式,而不要使用变量: 类型


仅限位置参数




在 Python 中,可以强制要求某些参数只能通过关键字参数或位置参数的方式进行传递。

# 这里的 * 表示参数 c 和 d 必须通过关键字参数的方式传递
# 所以即便非默认参数 d 在默认参数 c 的后面也没有关系
def foo(a, b, *, c=123, d):
    pass
# / 要求它前面的参数(这里是 a 和 b) 必须通过位置参数的方式传递
def bar(a, b, /, c, d):
    pass

但 Cython 在 0.29 的时候,只支持仅限关键字参数(*),不支持仅限位置参数(/)。而 Cython 在 3.0 的时候,这两者则都支持。


赋值表达式




这是 Python3.8 新增的一个功能,可以在表达式当中完成赋值。

import re
date = "2020-03-04"
match = re.search(r"(\d{4})-(\d{2})-(\d{2})", date)
if match is not None:
    year, month, day = match.groups()
    print(year, month, day)  # 2020 03 04
# 通过赋值表达式,可以将赋值和比较一步完成
if (match := re.search(r"(\d{4})-(\d{2})-(\d{2})", date)) is not None:
    year, month, day = match.groups()
    print(year, month, day)  # 2020 03 04

赋值表达式是我个人比较喜欢的一个功能,但 Cython 在 0.29 以及之前是不支持的,从 3.0 开始才支持。


接下篇:https://developer.aliyun.com/article/1617409


相关文章
|
6月前
|
开发者 Python
Python 之父爆料:明年至少令 Python 提速 1 倍!
Python 之父爆料:明年至少令 Python 提速 1 倍!
41 0
|
4月前
|
监控 测试技术 Python
颠覆传统!Python闭包与装饰器的高级实战技巧,让你的项目效率翻倍
【7月更文挑战第7天】Python的闭包与装饰器是强大的工具。闭包是能记住外部作用域变量的内部函数,常用于动态函数创建和工厂模式。例如,`make_power`返回含外部变量`n`的`power`闭包。装饰器则允许在不修改函数代码的情况下添加新功能,如日志或性能监控。`my_decorator`函数接收一个函数并返回包装后的函数,添加了前后处理逻辑。掌握这两者,可提升编程效率和灵活性。
40 3
|
1月前
|
缓存 编译器 API
详解历时五年的 Cython3.0 都发生了哪些变化(二)
详解历时五年的 Cython3.0 都发生了哪些变化(二)
26 1
|
5月前
|
前端开发 PHP 开发者
TIOBE 6月榜单:PHP稳步前行,编程语言生态的微妙变化
PHP在TIOBE 6月榜单上升至第15位,彰显其在Web开发的持久力。PHP得益于深厚的Web根基、框架的成熟、性能优化和活跃的社区支持。排名变化反映技术生态多样性,强调成熟语言的长尾效应、生态重要性和持续改进的价值。PHP正与新兴技术融合,如Docker和前端框架,同时在企业级应用中展现实力。尽管面临性能、类型安全和云原生的挑战,PHP社区的创新将继续影响其未来。
107 9
|
缓存 Rust JavaScript
性能最快的代码分析工具,Ruff 正在席卷 Python 圈!
性能最快的代码分析工具,Ruff 正在席卷 Python 圈!
241 0
python-分水岭-技术指标
python-分水岭-技术指标
158 0
|
Rust JavaScript 前端开发
开发者调查报告:JavaScript 和 Python 仍占主导地位,Rust 持续猛增 4 倍
开发者调查报告:JavaScript 和 Python 仍占主导地位,Rust 持续猛增 4 倍
132 0
|
Rust 机器人 编译器
Rust 公布 2024 年路线图:重点涉及三个方向
Rust 公布 2024 年路线图:重点涉及三个方向
471 0
|
存储 SQL 数据挖掘
还在抱怨pandas运行速度慢?这几个方法会颠覆你的看法
还在抱怨pandas运行速度慢?这几个方法会颠覆你的看法
1016 0
还在抱怨pandas运行速度慢?这几个方法会颠覆你的看法
|
Java 程序员
2020 年软件开发趋势预测,Java 将占主导,Python 将要吞噬世界。
全面云计算时代宣告来临,微服务已成软件架构主流,Kubernetes 将会变得更酷,2020 年还有哪些技术趋势值得观察?
2525 0