Python3.12的发布也意味着距离Python3.14只差两个版本,那时应该称Python为π-thon?(谐音哏扣钱 O(∩_∩)O哈哈~
Improved Error Messages
首先,Python3.12改进的第一件事是错误信息的优化。
例如标准库中的模块会在错误信息中给出明确的提示,如果你使用sys的某项功能,但是没有导入sys模块,它就会在报错信息中直接提示“Did you forget to import 'sys'?”(虽然是件小事,并且现在的集成开发工具也会给出一些语法错误的提示,但是对于新手同学来说还是比较友好的。
另外,错误信息也更聪明了,例如一些同学经常犯的错误from y import x
写成import x from y
,错误信息更加明确,并且会给出正确操作建议:
另外一个比较智能的提示就是如果你引用的某个变量属于self,但是忘写self.
,名字也会在错误信息中给出明确的提示:
最后,是ImportError的改进,例如导入模块中的某些东西未生效,那么它会建议你做更正,并给出更正建议:
以上错误信息提示都是一些相对较小的改进,但总体来说,它们确实让Python的编码体验更好,对于新手入坑也更加友好。(将来也许可以用AI来生成更加有针对性的错误信息数据库?
性能提升
第二件事就是性能的提升,性能
几乎是Python每个版本都会提及的话题。
在Python3.11中,官方表示在性能提升方面是一个巨大的飞跃(官方文档原话:*Python 3.11 is between 10-60% faster than Python 3.10. On average, we measured a 1.25x speedup on the standard benchmark suite. See Faster CPython for details.*)。
而现在从Python3.11到Python3.12差别不大,但还是有几处调整。
Comprehension inlining
PEP 709: Comprehension inlining
其中一项改进是解析式上下文,字典、列表和集合推导式使用内联,而不是为推导式的每次执行创建一个新的一次性函数对象。这可以将解析的执行速度提高两倍
。推导式是Python一个很好的功能,允许我们创建一个列表只用几条简单的规则,在实际应用中也非常广泛。
immortal objects
PEP 683 – Immortal Objects, Using a Fixed Refcount
Python3.12中添加的另一个功能immortal objects(不朽对象),原则上使用Python每个对象都有一个引用计数,用于跟踪对象仍在使用,有些对象是用户创建的,随时都有可能不再需要,而有些对象是系统创建,总是被系统需要,这就是immortal objects,所以它们的引用计数从未更新,保持不变。
现在看来,可能是件微不足道的小事,但它可能对将来的Python有重大影响。我们可以用immortal objects表示一个对象是基本上永远不会改变,如果要避免缓存失效(cache invalidations),避免竞态条件(data race),在这种情况下多个代码要修改相同的数据,如果它们是immortal objects,这就不会改变,这样程序就变得简单多了。
A Per-Interpreter GIL
PEP 684: A Per-Interpreter GIL
Python3.12的下一个新特性A Per-Interpreter GIL给每个子解释器创建GIL
。
那么,什么是GIL?
Global Interpreter Lock 全局解释器锁。
GIL是Python解释器中的全局锁,它是一种机制,确保同一时刻只有一个线程在执行Python代码。在多线程程序中,因为GIL的存在,多线程并不能真正并行执行,而是通过竞争GIL的方式来实现对CPU的占用。这也是为什么Python中的多线程程序并不比单线程程序更快的原因之一。
这实际上是Python工作方式的重大改变,PEP 684 由“香农计划”的作者Eric Snow提出,主要是给每个子解释器创建GIL。这使得Python程序能够充分利用多个CPU内核,允许Python实现真正的并行处理。
但是目前只能通过C-API获得,预计3.13中将提供Python API,将会有一个新模块叫做Stdlib。(通过从每个进程一个GIL过渡到每个子解释器一个GIL,其实是Python一个很大的更新,GIL长久以来广受诟病,但没人提出一个合理的解决方案,现在有了,可以看出Python正在采取措施改善多线程并行性。
Optimizations
文档:https://docs.python.org/3/whatsnew/3.12.html#optimizations
还有一些其他的优化也可能有助于改善性能。例如wstr,从Unicode对象中删除wstr和wstr_length成员。它在64位平台上将对象大小减少了8或16字节。这样就可以节省内存,并有可能避免缓存失误及类似的情况。
f-strings
Python3.12的更新使得f-strings的使用变得不那么挑剔,新版取消了最初制定f-strings时的一些限制(最初设置f-strings限制是为了能够在不修改现有词法分析器的情况下将f-strings的解析实现到CPython中,但目前看来,这些限制反而带来了复杂性)。
Python3.12的这些优化将会为终端用户和库开发者带来较大优势,同时也大大降低用于解析f-strings代码的维护成本。
允许重用相同的引号
在3.12之前的版本中以下代码是不被允许的,也就是重用与封闭的f-strings相同的引号会引发SyntaxError(如果f-strings使用单引号,则内部的字符串需要使用双引号或三引号),这意味着我们不得不用不同类型的引号,实际上是一个颇为困扰的问题。
代码语言:javascript复制def main() -> None:
# my_str = f'Hello,{input("Enter your name: ")}!'' # Python3.12之前用法
my_str = f"Hello,{input("Enter your name: ")}!"
print(my_str)
但是现在,这种形式是被允许的,同时也有助于嵌套字符串的运用。虽然是小修小改,但有这个功能还是很不错的。
嵌套字符串:
代码语言:javascript复制def main() -> None:
print(f"""{f'''{f'{f"{1 1}"}'}'''}""")
print(f"{f"{f"{f"{f"{f"{1 1}"}"}"}"}"}")
if __name__ == '__main__':
main()
(但是这样一旦出错就很难查找引号的匹配问题了?
多行表达式和注释
在Python3.11中,f-strings表达式必须在单行中定义,而Python3.12支持定义多行的f-strings,并且可以添加内联注释:
代码语言:javascript复制# author: 测试蔡坨坨
# datetime: 2023/11/5 12:06
# function: Python3.12
def main() -> None:
my_str = f"This is the playlist: {", ".join([
'《天外来物》', # 你就像天外来物一样,求之不得
'《陪你去流浪》', # 我会攥着小糖,眺望你方向
'《小尖尖》' # 我口袋只剩玫瑰一片 此行又山高路远
])}"
print(my_str)
if __name__ == '__main__':
main()
支持反斜杠“”和Unicode字符
在Python3.12之前,f-strings表达式不能包含任何字符。这也影响了Unicode转义序列,因为它们包含以前不能成为f-strings表达式组件一部分的“N”部分。现在Python3.12中f-strings表达式可以包含反斜杠,因此也就可以使用Unicode字符,如下所示:
# author: 测试蔡坨坨
# datetime: 2023/11/5 12:06
# function: Python3.12
def main() -> None:
names = ['测试蔡坨坨', '小趴蔡', '薛之谦', 'joker']
print(f"My name is: {"n".join(names)}")
print(f"My name is: {"N{BLACK HEART SUIT}".join(names)}")
if __name__ == '__main__':
main()
TypedDict
PEP 692: Using TypedDict for more precise **kwargs typing
Python3.12引入了TypedDict类型构造函数,支持字符串键
和可能不同类型的值
组成字典类型。
如下所示:
代码语言:javascript复制# author: 测试蔡坨坨
# datetime: 2023/11/5 12:06
# function: Python3.12
from typing import TypedDict, Unpack
class Movie(TypedDict):
name: str
year: int
def foo(**kwargs: Unpack[Movie]):
print(type(kwargs))
print(kwargs)
def main() -> None:
movie: dict[str, object] = {"name": "《演员》", "year": 2015}
foo(**movie)
type_movie: Movie = {"name": "《念》", "year": 2023}
foo(**type_movie)
if __name__ == '__main__':
main()
装饰器@override
PEP 698: Override Decorator for Static Typing
Python3.12中还出现的另一个功能,就是新增的装饰器@override关键字,这个功能的作用就是明确指出一个方法覆盖另一个方法,如下所示:
代码语言:javascript复制from typing import override
class Base:
def get_color(self) -> str:
return "blue"
class GoodChild(Base):
@override # ok: overrides Base.get_color
def get_color(self) -> str:
return "yellow"
class BadChild(Base):
@override # type checker error: does not override Base.get_color
def get_colour(self) -> str:
return "red"
注意同名覆盖,函数名字不同则不会覆盖。
新泛型语法
PEP 695: Type Parameter Syntax
PEP 484 下的泛型类和函数是使用冗长的语法来声明的,这使得类型参数的范围不明确,并且需要显示的声明。
PEP 695 引入了一种新的、更紧凑和明确的方法来创建泛型类和函数。
先举个例子看看效果,例如定义一个向量数据类型:
代码语言:javascript复制type Vector = list[float]
def scale[T](scalar: float, vector: Vector) -> Vector:
return [scalar * num for num in vector]
# passes type checking; a list of floats qualifies as a Vector.
new_vector = scale(2.0, [1.0, -4.2, 5.4])
print(new_vector)
# [2.0, -8.4, 10.8]
将以上代码抽象出来,之前定义泛型类的做法如下:
代码语言:javascript复制from typing import Generic, TypeVar
_T_co = TypeVar("_T_co", covariant=True, bound=str)
class ClassA(Generic[_T_co]):
def method1(self) -> _T_co:
...
而在新语法中,我们只需要如下:
代码语言:javascript复制class ClassA[T: str]:
def method1(self) -> T:
...
之前定义泛型函数:
代码语言:javascript复制from typing import TypeVar
_T = TypeVar("_T")
def func(a: _T, b: _T) -> _T:
...
新语法定义泛型函数:
代码语言:javascript复制def func[T](a: T, b: T) -> T:
...
在新语法中不再需要TypeVar类型强制,只需要用[T]
指定,与其他一些编程语言类似。
另一个例子,定义泛型类别名的例子,之前的语法:
代码语言:javascript复制from typing import TypeAlias
_T = TypeVar("_T")
ListOrSet: TypeAlias = list[_T] | set[_T]
新语法使用type语句声明一个类型别名,即typing.TypeAliasType的实例:
代码语言:javascript复制type ListOrSet[T] = list[T] | set[T]
其他
另外Python3.12还有一些小的改动,例如pathlib新增了pathlib.Path.walk()方法,用于遍历目录树并生成其中的所有文件和目录名称,类似os.walk();CPython支持监控调用的功能,用于监视CPython中的事件,包括调用、返回、异常等;pdb添加方便的变量来临时保存调试会话的值,并提供对当前帧和返回值等值的快速访问(pdb其实就是Python官方提供的命令行debugger工具,在以后的文章中再做详细介绍,这里就不展开了)……
最后就是Python3.12中移除了多个已经弃用的模块,比如asynchat和asyncore已经被asyncio取代;distutils被删除,因为它在Python3.10已经被弃用,而现在通常会使用setuptools来替代;还有一些单元测试unittest中的一些长期弃用的内容也被删除了,不过也不用担心,因为有些方法你可能见都没见过。(更多被删除的内容可以参考官方文档:https://docs.python.org/3/whatsnew/3.12.html#removed。
以上就是我认为Python3.12一些比较有意思的更新点,更多更新内容可参考官方文档:https://docs.python.org/3/whatsnew/3.12.html。
以上,完。
脚踏实地,仰望星空,和坨坨一起学习软件测试,升职加薪!