前面整一篇只讲了 Python 3.11 的新语法特性,异常组与 except*
, 这篇说其他的。
可为异常添加备注
在 BaseException 上新加了一个实例方法 add_note(self, note),在捕获到异常后可进一步润色而无需创建一个新的异常再次抛出。
内置 tomllib 支持 TOML 配置文件
TOML 是 Tom's Obvious Minimal Language, 像 INI 文件,但表述性更强,支持丰富的数据类型。现代新型的 Python 项目依赖管理构建工具都使用了 pyproject.toml 文件,如 Poetry, uv, 以及 PDM。pyproject.toml 在 Python 3.6 就引入了,见 PEF 518,但似乎一直被顽固的 Python -m venv 忽略。
从 Python 3.11 开始新加了对 TOML 配置文件的编程接口,像使用 JSON 和 Pickle 一样的 load() 和 loads() 反序列方法,没有序列化方法
1 2 3 4 |
import tomllib with open("pyproject.toml", 'rb') as f: data = tomllib.load(f) |
data 得到的是一个 dict[str, Any]。tomllib.loads("<toml_str>") 是从一个 TOML 配置字符串加载为字同典。
tracebacks 中用波浪线显示更精确的错误代码的位置,而不仅指示到错误代码行,这个不影响到编程,方便于 Debug
新的 -P 命令行参数和环境变量 PYTHONSAFEPATH 避免了潜在不安全的路径添加到 sys.path 中
sys.path 中的列表告诉 Python 解析器从哪里加载脚本或包,模块,见 Python 的模块搜索路径 中有对它详尽的描述, 以及可通过环境变量 PYTHONPATH 附加模块的搜索路径。
首先看新加的参数 -P 的效果,假如有一段打印 sys.path 内容的代码 demo.py
1 2 |
import sys print('\n'.join(sys.path)) |
不带 -P 参数执行 python demo.py
1 2 3 4 5 6 |
venv_311/bin/python demo.py /Users/yanbin/Workspaces/test-python /opt/homebrew/Cellar/python@3.11/3.11.13/Frameworks/Python.framework/Versions/3.11/lib/python311.zip /opt/homebrew/Cellar/python@3.11/3.11.13/Frameworks/Python.framework/Versions/3.11/lib/python3.11 /opt/homebrew/Cellar/python@3.11/3.11.13/Frameworks/Python.framework/Versions/3.11/lib/python3.11/lib-dynload /Users/yanbin/Workspaces/test-python/venv_311/lib/python3.11/site-packages |
看到当前路径添加在了 sys.path 的最前端
带上 -P 参数执行 python -P demo.py
1 2 3 4 5 |
venv_311/bin/python -P demo.py /opt/homebrew/Cellar/python@3.11/3.11.13/Frameworks/Python.framework/Versions/3.11/lib/python311.zip /opt/homebrew/Cellar/python@3.11/3.11.13/Frameworks/Python.framework/Versions/3.11/lib/python3.11 /opt/homebrew/Cellar/python@3.11/3.11.13/Frameworks/Python.framework/Versions/3.11/lib/python3.11/lib-dynload /Users/yanbin/Workspaces/test-python/venv_311/lib/python3.11/site-packages |
这样就防止了把当前路径添加进 sys.path, python 解析器只读取当前路径上的 demo.py,而避免了加载当前路径中的其他可能不安全的模块,比如在当前路径中创建与其他库同名的模块,以达到替换实现的目的。
对于 python -m module 和 python -c code 或 python 进入 REPL 时加上 -P
参数也能实现一样的阻止当前目录作为模块搜索路径。
设置了 PYTHONSAFEPATH 为非空值时,相当于是 python 带了 -P
参数,例如
1 2 3 4 5 |
PYTHONSAFEPATH=xx venv_311/bin/python demo.py /opt/homebrew/Cellar/python@3.11/3.11.13/Frameworks/Python.framework/Versions/3.11/lib/python311.zip /opt/homebrew/Cellar/python@3.11/3.11.13/Frameworks/Python.framework/Versions/3.11/lib/python3.11 /opt/homebrew/Cellar/python@3.11/3.11.13/Frameworks/Python.framework/Versions/3.11/lib/python3.11/lib-dynload /Users/yanbin/Workspaces/test-python/venv_311/lib/python3.11/site-packages |
相当于是 python -P demo.py
新增了 TypeVarTuple 支持 Variadic 泛型
TypedDict 中可表示某个 key 是必须还是可选
1 2 3 4 5 6 7 8 9 10 |
from typing import TypedDict, NotRequired class Movie(TypedDict): title: str year: NotRequired[int] m1: Movie = {"title": "Black Panther", "year": 2018} m2: Movie = {"title": "Star Wars"} m3: Movie = {"year": 2022} # TypedDict 'Movie' has missing key: 'title' |
默认 key 是 Required, 如 title: str
相当于是 title: Required[str]
. 但 TypedDict 毕竟是 typing 的范畴,Python 解析器执行时没有强制,只是通不过像 mypy 那种工具的关。
这里学到了一个新技能,以前想要一个模型类的话,选择是 @dataclass, 或 NamedTuple, 显然用 TypeDict 要方便。
用 Self 来表示方法返回一个自身实例
比如一个方法返回自身实例 self, 试图下面那样写
1 2 3 4 |
class MyLock: def __enter__(self) -> MyLock: self.lock() return self |
Python 解析不过
1 2 3 |
def __enter__(self) -> MyLock: ^^^^^^ NameError: name 'MyLock' is not defined |
因为此时 MyLock
还不存在,但可以用引号扩起来
1 |
def __enter__(self) -> "MyLock": |
但我们尽量避免用字符串作为类型注解,因为字符串是动态的,在 Python 3.11 中引入的 Self
来解决
1 2 3 4 5 6 |
from typing import Self class MyLock: def __enter__(self) -> Self: self.lock() return self |
LiteralString 注解规定函数参数只接受字面常量字符串
字符串变量就不行
1 2 3 4 5 6 7 8 9 10 11 12 |
import sys from typing import LiteralString def run_query(name: LiteralString): pass run_query("select * from students") str_var=f"select * from students where {sys.argv[1]}" run_query(str_var) # Expected type 'LiteralString', got 'str' instead def bar(xxx: LiteralString): run_query(xxx) # 这是合法的,因为 xxx 也是一个 LiteralString |
这有什么意义呢?在静态代码扫描时确保传入的字符串是安全的,比如不允许通过动态的方式构建一个不安全的 SQL 语句
select * from students where 1=1; delete from students
PEP 681: Data class transforms
dataclass_transform 可用过创建某些共性的模型类,应该可增强 dataclass 的行为。这里打个标记,以后真有需求时再详细研究。
最后学一个 Python 3.9 就支持的在 for 语句中用 *
星号的语法
1 2 3 4 5 6 7 |
for a, b, c in [(1, 2, 3), (4, 5, 6)]: print(a, b, c) print('---------') for a, *b in [(1, 2, 3), (4, 5, 6)]: print(a, b) |
输出
1 2 3
4 5 6
---------
1 [2, 3]
4 [5, 6]
总结
Python 3.11 从语言上好像没带来太亮眼的新特性,只是到了发布周期必须出一个版本而已。最主要的就是增加了 Exception Group 的 except* 语法,然而似乎应用性不大,如果 except
和 except*
搞混了,不会有语法错误,但极有可能引入替在的 Bug。
我们在阅读 What's New In Python 3.11 的时候, Improved Modules 部分虽不是重头戏,最好也别放过。其中描述了哪些 API 已删除,在使用 IDE 写 Python 代码时根据选择的 Python 版本会被识别出来。但新加的 API 还是很有用处的,如
datetime.UTC,再也不用写成 datetime.timezone.utc 了
EnumMeta 改成了 EnumType
新增了 StrEnum 用了专门表示字符串类型的枚举
使用 gzip.compress() 时指定 mtime=0 的话(即 gzip.compress(data=..., mtime=0)),压缩变快,因为它用调用 zlib.compress() 操作
math 库方面,新增了 math.exp2() 平方操作,因为太常用了。新增了 math.cbrt() 立方根.
新增了 operator.call 操作,如果一个 obj 是 callable, obj(*args, **kwargs) 可以写成 operator.call(obj, *args, **kwargs)
pathlib 的 glob() 和 rglob() 在 pattern 以路径分隔符(os.sep 或 os.altsep) 结尾的情况只列出目录, 比如下面的代码
1 2 3 4 |
from pathlib import Path for p in Path(".").glob('*/'): print(p, p.is_dir()) |
Python 3.10 执行
venv_310/bin/python demo.py
venv_311 True
venv_310 True
test-python.iml False
test.py False
.mypy_cache True
.gitignore False
venv_312 True
demo.py False
venv_313 True
.idea True
Python 3.11 执行
venv_311/bin/python demo.py
venv_311 True
venv_310 True
.mypy_cache True
venv_312 True
venv_313 True
.idea True
增加了 ZipFile.mkdir() 在 ZIP 文件内部创建目录
Python 3.11 还没有使用 JIT 编译
本文链接 https://yanbin.blog/python-3-11-key-new-features-others/, 来自 隔叶黄莺 Yanbin Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。