
3.12 有几个改动属于“知道之后代码立刻能短一截”的类型。下面这些都在本机 Python 3.12.10 上跑过输出是脚本实跑的结果。泛型不用再 import TypeVar 了以前写一个泛型栈开头得先摆三行样板fromtypingimportGeneric,TypeVar TTypeVar(T)classStack(Generic[T]):...3.12 的 PEP 695 把TypeVar变成了语法的一部分方括号直接写在类名和函数名后面classStack[T]:def__init__(self)-None:self._items:list[T][]defpush(self,item:T)-None:self._items.append(item)defpop(self)-T:returnself._items.pop()deffirst[T](items:list[T])-T|None:returnitems[0]ifitemselseNone类型参数是真实存在的对象不是装饰器语法糖Stack.__type_params__ (T,) first.__type_params__ (T,)好处是两件事少一个 import以及类型参数不再泄漏到模块命名空间里。以前T TypeVar(T)是模块级的一个真实对象整个文件都看得见它。PEP 695 的类型参数只活在声明它的类或函数内部出了这个范围就看不到同名也不会撞车。声明完之后在模块级打印它就能看到差别print(T)# NameError: name T is not defined旧写法的T一直到模块级都能取到两者不是同一个对象。type 语句类型别名终于是个正式语法以前写类型别名有两种写法都不太理想Vectorlist[float]# 就是个普通变量, 表达不了泛型参数Vector:TypeAliaslist[float]# 得从 typing 导入 TypeAlias3.12 加了type语句typeVectorlist[float]typeMatrix[T]list[list[T]]它和普通赋值的关键区别是type语句声明的是「类型别名」这件事本身而不是一个恰好指向类型的变量。它支持泛型参数运行时也是独立的别名对象Matrix[int] - [[1, 2], [3, 4]] Matrix.__type_params__ (T,)读代码时的收益比写代码时更大——看到type开头就知道这是别名不是某个模块级常量。itertools.batched分批终于不用自己写把列表按 N 个一组切开这个需求几乎每个项目里都有一段手写的实现而且尾巴那组经常处理错。3.12 加了标准实现importitertools datalist(range(7))print(list(itertools.batched(data,3)))[(0, 1, 2), (3, 4, 5), (6,)]最后一批不足 3 个照常返回(6,)。返回的是元组不是列表。有个版本细节要提醒strictTrue参数是 3.13 才加的3.12 用了会直接报错TypeError: batched() takes at most 2 arguments (3 given)网上有些文章把 3.13 的用法和 3.12 混在一起写照着抄会在这里卡住。f-string 的三条限制解除了PEP 701 把 f-string 的解析器重写了。下面这三件事在 3.11 上都是语法错误3.12 才能跑。三条报错都在本机 3.11.7 上实跑过nameXianLin# 1. 表达式里用与外层相同的引号类型# 3.11 报 SyntaxError: f-string: expecting }print(f同类引号: {paid if True else x} / {name})# 2. 表达式里出现反斜杠。内外引号必须不同# 否则先撞上第 1 条看不到这条限制自己的报错# 3.11 报 SyntaxError: f-string expression part cannot include a backslashprint(f换行符:{a\nb.splitlines()})# 3. 表达式跨多行# 3.11 报 SyntaxError: unterminated string literal (detected at line 1)print(f多行表达式:{sum(i*2foriinrange(4))})本机 3.12.10 的实际输出同类引号: paid / XianLin 换行符: [a, b] 多行表达式: 12这三行逐字复制自verify_312_features.py的完整输出证据文件里可对账。3.11 上写 f-string 里的字典取值外层用双引号时内层必须换成单引号嵌套两层就得数引号# 3.11 得这么写内外引号必须不同print(f{data[key]})3.12 之后内外可以是同一种引号嵌套多层也不用换。不过第三条有个反面用法值得提多行表达式虽然合法但别在 f-string 里塞复杂逻辑。上面的sum(...)用来演示可以真实代码里抽成变量更好读。typing.override拼错方法名能被查出来子类覆盖父类方法时方法名打错是很常见的手误运行时完全看不出来——你以为覆盖了实际上父类的方法还在生效。fromtypingimportoverrideclassBase:defrun(self)-str:returnbaseclassChild(Base):overridedefrun(self)-str:returnchildoverride在运行时只给函数打一个__override__ True的标记不包装饰器、不改调用路径。它的价值在类型检查阶段父类里没有同名方法时比如你把run打成了runnmypy 和 pyright 会据这个标记报错。收益全在静态检查调用开销不变。列表推导式比 append 循环快但别过度解读3.12 的 PEP 709 把列表推导式内联了不再为每次推导创建独立的函数帧。实测对比推导式 0.156~0.173s vs append 循环 0.185~0.197s (推导式 1.14~1.18x)上面给的是区间而不是单个值三次跑的是同一段代码单次值在证据文件里。三次方向一致都是推导式更快。计时受机器负载影响单次值不该当结论这就是给区间的原因。推导式本来就比手写append快所以这个数字不能直接当成3.12 比 3.11 快 14%~18%。要用它判断版本收益得在两个 Python 版本上跑同一段代码才有意义。这里放出来只是说明一件事推导式不是语法糖它是有性能理由的写法。报错信息给的建议更全了这一节要分清版本因为两个建议不是同一批加的本机 3.11.7 与 3.12.10 对跑核实属性名建议早于 3.12本机 3.11.7 上就有导入名建议from os import pth提示Did you mean: path?才是3.12新增的3.12 的改动是把建议扩展到 ImportError 这一类并让消息组织得更清楚。所以严格说这一节讲的是「3.12 补齐了导入名建议」不是「3.12 开始给建议」。两类拼写错误现在都有候选importitertools itertools.batchd# AttributeErrorAttributeError: module itertools has no attribute batchd. Did you mean: batched?同一条语句在 3.11.7 上没有候选3.11 的itertools里压根没有batched这个名字猜也没得猜。fromosimportpth# ImportErrorImportError: cannot import name pth from os (安装路径\Lib\os.py). Did you mean: path?尖括号处的安装路径已省略。同一句在 3.11.7 上照样报错只是没有后半句建议。注意这里有个容易搞错的细节ModuleNotFoundError不会给建议。实测import itertool输出仍然是干巴巴的一行ModuleNotFoundError: No module named itertool规律是模块内部的名字猜错了会提示整个模块找不到时不提示——毕竟模块可能压根没装猜也没意义。一个提醒上面这些改动里class Stack[T]和type Vector ...都是语法级改动。语法一旦用了代码就再也跑不回 3.11 及以下——不是 import 不到东西那种能靠 try/except 兼容的情况而是直接 SyntaxError。所以用之前先确认两件事运行时的 Python 版本以及 CI 里用的是哪个版本。有库作者在本地用 3.12 写得顺手推上去之后 CI 跑 3.10 直接挂掉。现在可以跑一下这两条确认自己的环境python-cimport sys; print(sys.version)python-cfrom typing import override; print(3.12)第一条低于 3.12 的话上面这些内容全都先别往项目里写——包括itertools.batched。它是 3.12 才加进标准库的3.11 上直接AttributeError不是「先别用新语法就行」那么简单。