继续感受新特性系列,这次看看于 2023 年 10 月 2 日发布的 Python 3.12 给我们带来了什么新特性。Python 3.14 预计在今年 10 月份推出,一定要对每年一个正式版的新东西有所了解。依旧是由官方的 What's New In Python 3.12 阅读进行展开。
Python 3.12 从编程上的观感变化不大,主要是移除了 distutils
, 增加了 f-strings
和 type
提示,其他对诸如迭代推导等的字节码优化,对性能的提示是不具有直接感受的。
Python 3.12 移除了 distutils(Distribution Utilities)
distutils 是一个 Python 内建的构建,打包和分布 Python 模块/包的工具集,其实对于多数应用开发人员对此也是感的。能与此能顺利建立关联的就是有些 Python 项目中的那个 setup.py 文件,那也是史前的产物。随着 Python 的快速流行,Python 的包管与构建工具也多了起来, 像 Poetry, PDM 和 uv, 尤其是 uv(Rust 编写的) 的闪电速度及全能表现有望成为 Python 界的 Maven。
注:提到 uv 时还应留意 AStRAL 的另外两个工具,RUFF: 用 Rust 写的轻量级,极速的 Python linter 工具; ty: 也是用 Rust 写的极速 Python 类型检查与 language server 工具。并且 uvx 命令将会与我们相伴随。又从 PyCharm 中看到一个新的 Python 工具链 Hatch。
distutils 移除后会给我们带来什么影响呢?对于老旧的依然使用 setup.py 的项目要升级到 Python 3.12 就要把 setup.py 中的
from distutils.core import setup
替换为
from setuptools import setup
所以 setuptools 是 distutils 的替代品,它功能更齐全,与现代工具链完全兼容,支持依赖自动安装,支持插件和自定义命令和 pyproject.toml 配置,它还是要用 twine 来上传包。不过使用了 Poetry, PDM 或 uv 的来管理项目的话,连 setuptools 是什么都不用去在意。
对于 python3.11 -m venv venv311 和 python3.12 -m venv venv312 生成的虚拟环境目录,bin 目录没有区别,区别在于 site-packages 中少了些东西
1 2 3 4 |
$ ls venv311/lib/python3.11/site-packages _distutils_hack distutils-precedence.pth pip pip-25.1.1.dist-info pkg_resources setuptools setuptools-80.9.0.dist-info $ ls venv312/lib/python3.12/site-packages pip pip-25.1.1.dist-info |
关于 distutils 的内容就说这些了,下面一个新特性是
f-strings 的增强
首先顺便回顾一下 f-strings 的比较全方位的功能。
f-strings 自 Python 3.6 推出之后,持续在增强功能,最初只是 'f' 或 'F' 前缀字符串中 {}
可用变量或表达式。3.8 添加了 =
, !s
, !r
, !a
的支持,假设 o=3
1 2 3 4 |
f"{o=}" # 输出 o=3 f"{o!s}" # 相当于 o.str(), 或 f"{o.str()} f"{o!r}" # 相当于 o.repr(), 或 f"{o.repr()}" f"{o!a}" # 相当于 o.ascii(), 或 f"{o.ascii()}" |
f-strings 中的冒号 :
会调用 __format__()
函数
1 2 3 4 |
>>> import datetime >>> today = datetime.datetime.now() >>> f"{today:%B %d, %Y}" 'August 05, 2025' |
比如可以自定义 __format__() 函数
1 2 3 4 5 6 7 |
class AA: def __format__(self, format_spec): return "formatted by " + format_spec if __name__ == '__main__': aa = AA() print(f"{aa:test}") |
输出
formatted by test
单行表达式,f"{x if x > 0 else y}"
. 或者下面的语法
1 2 3 4 |
x=1 y=2 z=3 a=f"{x, y, z}" # 相当于 a="(1,2,3)" |
其实 {}
中间罗列多个相当于是一价目 tuple, 完整的语法是
1 |
a=f"{(x, y, z)}" |
想像一下某个函数接受参数类型为 tuple 时,我们只有一个元素时就要写成以下调用形式
1 |
foo((a,)) |
回顾完了,开始 Python 3.12 对 f-strings 的增强
f-strings 中可包含与外层相同的引号
好似最外层的引号(或{})让中间的相同的引号自动进行的转义
1 2 3 |
version = {"major": 1, "minor": 2} print(f"{version["major"]}") print(f'{', '.join(version.keys())}') |
或者像官方文档中的例子,实现 f-strings 嵌套
1 |
a=f"""{f'''{f'{f"{1+1}"}'}'''}""" # a = '2' |
f-strings 大括号可写成多行
实际上也是因了上条的原因,f-strings 能够匹配前后两个大括号,所以大括号中间可以换行,还能加上注释
1 2 3 4 |
version = {"major": "1", "minor": "2"} print(f"versions: { ", ".join(version.values()) # major, minor versions }") |
输出
versions: 1, 2
f-strings 可包含 \ 转义符
在 Python 3.12 之前,即使避开用外层相同的引号也不能用 \
转义符
1 2 |
version = {"major": "1", "minor": "2"} print(f"{'\n'.join(version.keys())}") |
以上代码在 Python 3.11 中报错
SyntaxError: f-string expression part cannot include a backslash
在 Python 3.12 中通过,就是全用相同的引号都行
1 2 3 |
version = {"major": "1", "minor": "2"} print(f"{'\n'.join(version.keys())}") print(f"{"\n".join(version.keys())}") |
同时包含 \ 和 {} 的例子
1 2 |
version = {"major": "1", "minor": "2"} print(f"This is the playlist: {"\N{BLACK HEART SUIT} ".join(version.keys())}") |
输出为
This is the playlist: major♥ minor
\N{BLACK HEART SUIT}
就是 ♥
type 表达式,即类型别名
Python 3.12 引入了一个像 C/C++ typedef 那样语法来定义类型
1 |
type Point = tuple[float, float] |
那么我们使用 Point
1 2 |
p1 = (1.0, 2.0) p2: Point = (1.3, 1.7) |
回顾一下我们在 Python 3.12 以前不显式用 class 创建类时可以怎么定义类型
namedtuple
1 2 3 |
Point = namedtuple('Point', ['x', 'y']) p1 = Point(1.0, 2.0) p1.x |
可用属性 x, y 来访问
TypedDict
1 2 3 4 |
from typing import TypedDict Point = TypedDict('Point', {'x': float, 'y': float}) p1 = Point(x=1.0, y=2.0) p1['x'] |
本质上还是一个 Dict
泛型实现更简单
以前实现一个 Python 的泛型类要用到 TypeVar, Generic
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
from typing import TypeVar, Generic T = TypeVar('T') class Cache(Generic[T]): def __init__(self): self.store = {} def put(self, key: str, value: T): self.store[key] = value def get(self, key: str) -> T: return self.store[key] cache = Cache[str]() cache.put("foo", "bar") print(cache.get("foo")) |
现在 Python 3.12 直接上 T
, 像是 Java 泛型的写法,上面的代码可简化为
1 2 3 4 5 6 7 8 9 |
class Cache[T]: def __init__(self): self.store = {} def put(self, key: str, value: T): self.store[key] = value def get(self, key: str) -> T: return self.store[key] |
省去了使用 Cache 类的代码。
type 语句中用泛型,也是直接用表示类型的字符(串)
1 2 3 4 5 |
type Point[T] = tuple[T, T] p1 = (1.0, 2.0) type Address[LINE1, LINE2] = tuple[LINE1, LINE2] a1 = ('line1', 'line2') |
可结合 Java 的泛型语法来理解。
其他 typing 相关的新特性
用 TypedDict 来准确的注解 **kwargs
上面刚回顾了 TypedDict,这就要派上用场,**kwargs
的参数太随意了,只知道是可以用 a=?, b=?, c=?
来传递的 keyword 类型的参数,再就没有具体的限制了。而用 TypedDict 注解 **kwargs
就能被类型系统或 IDE 识别出具体能接受参数的 keywaord
直接用官方的例子
1 2 3 4 5 6 7 8 |
from typing import TypedDict, Unpack class Movie(TypedDict): name: str year: int def foo(**kwargs: Unpack[Movie]): pass |
现在试图调用
1 |
foo(name="The Matrix", xyz=1999) |
用 mypy 就能检验出参数错误
test.py:7: note: "foo" defined here
test.py:10: error: Unexpected keyword argument "xyz" for "foo" [call-arg]
Found 1 error in 1 file (checked 1 source file)
用 ty check test.py
,尚不识别 TypeDict 的约束,提示 All checked passed!
类似 Java 的 @override 的 typing.override() 装饰器
和 Java 中的 @override 是一样的意思,表示覆盖父类中的方法
1 2 3 4 5 6 |
from typing import override class AAA(object): @override def __str__(self): return "haha" |
对于父类中不存在的方法应用了 @override 也同能被 mypy 检测出来。
引入 typing.override 时注意到还有 typing.overload,它可用来模拟出 Python 的重载方法,然而 @overload 装饰的方法却不能有实现,只用能声明支持的重载方法签名,实现要统一写在那个同名的但没有 @overload 装饰的方法中。此处不作引申。
可通过 python -m sqlite3 命令进入 sqlite shell
本地没有安装独立的 sqlite3, 用 Python 自带的就行
1 2 3 4 5 6 7 8 9 10 |
python -m sqlite3 sqlite3 shell, running on SQLite version 3.50.2 Connected to a transient in-memory database Each command will be run using execute() on the cursor. Type ".help" for more information; type ".quit" or CTRL-D to quit. sqlite> create table users(id, name); sqlite> insert into users values(1, 'Anna'); sqlite> select * from users; (1, 'Anna') |
python -m uuid 命令产生 uuid
连安装 uuid, 或 guid 命令都省了,Python 提供的快捷工具还真不少。
python -m uuid
1adc93b0-cea0-42da-ba1d-94a9fbc67d43
用 -u {uuid1,uuid3,uuid4,uuid5} 参数选择 uuid 的类型
其他
其他更友好的错误建议提示,推导的内联优化, isinstance(), asyncio 的性能提升。每个解释器单独的 GIL(全局解释锁),这可让子进程更有效的使用多核 CPU。
当 type-except*
处理整个 ExceptionGroup
并抛出另一个异常时,该异常不再包装为 ExceptionGroup.
新方法 pathlib.Path.walk(), 类似于 os.walk(), 看来有些 os 的操作可考虑用 pathlib 中类似方法。
本文链接 https://yanbin.blog/python-3-12-new-features/, 来自 隔叶黄莺 Yanbin Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。
很棒的网站,感谢分享