跳到主要内容

现代 Python 你应该知道的 10 个特性

· 阅读需 6 分钟
Destiny
自动控制 + 强化学习博士

很多人写 Python 还停留在 3.6 时代的写法:Optional[int]os.path.joinif-elif-else 一长串。其实从 3.10 开始,Python 已经悄悄变成了一门「看起来很像新语言」的语言。

我自己去年从 3.9 升级到 3.12 时,最大的感受是:不是 Python 变快了,而是写法变优雅了。很多以前需要三五行才能表达的逻辑,现在一行就搞定。下面这 10 个特性,是我在日常代码里几乎天天都会用到的。本教程默认 Python 3.12+,所以放心大胆地用。

1. match-case:结构化模式匹配(3.10)

不再是简单的 switch,它支持解构:

def describe(point: tuple[int, int]) -> str:
match point:
case (0, 0):
return "原点"
case (x, 0):
return f"在 x 轴上,x={x}"
case (0, y):
return f"在 y 轴上,y={y}"
case (x, y):
return f"普通点 ({x}, {y})"

print(describe((3, 0))) # 在 x 轴上,x=3

以前写这种分支逻辑,if-elif 链能拉到屏幕外面去。现在 match-case 不仅更短,而且意图更清晰——你一眼就能看出每种模式长什么样。

提示

match-case 还能匹配类实例、字典、序列,配合「守卫表达式」(case Point(x, y) if x == y) 非常强大。我在做状态机解析时用它替代了一大堆 if-elif,代码量直接减半。

2. 海象运算符 :=(3.8,但 3.10 才流行起来)

赋值的同时返回值,省掉一次重复调用:

import re

if (m := re.search(r"\d+", "订单号: 9527")) is not None:
print(f"提取到数字:{m.group()}") # 提取到数字:9527

第一次看到这个语法时我觉得很丑,用了之后发现——真香。尤其是在 while 循环和列表推导式里,它能让你避免把同一段逻辑写两遍。

3. 类型注解 X | Y(3.10)

终于不用再写 Union[int, str]Optional[int]

def find_user(user_id: int) -> dict | None:
if user_id == 1:
return {"name": "destiny"}
return None

# 等价于 Optional[dict] / Union[dict, None]

类型注解这东西,不用的时候觉得多余,用了之后发现 IDE 的自动补全和错误提示好了不止一个档次。X | Y 的写法让类型注解终于不那么「劝退」了。

4. dataclass + slots(3.10)

一行 slots=True 让 dataclass 用 __slots__,省内存又禁止动态属性:

from dataclasses import dataclass

@dataclass(slots=True)
class Point:
x: float
y: float

p = Point(1.0, 2.0)
# p.z = 3.0 # AttributeError,防止手滑加错字段

以前写数据类要手写 __init____repr____eq__,现在 @dataclass 一个装饰器全搞定。加上 slots=True,既有 Python 的简洁,又有 C 的效率。

5. f-string 嵌套与调试输出(3.12)

3.12 终于支持在 f-string 的 {} 里再写 f-string,=调试语法也更好用:

user = {"name": "destiny", "level": 5}
width = 12

# 嵌套 f-string
print(f"{f"{user['name']:^{width}}"}") # destiny

# 调试语法 = 直接打印变量名和值
print(f"{user=}") # user={'name': 'destiny', 'level': 5}

调试的时候 f"{x=}"print(f"x = {x}") 方便太多了——不用手动打变量名,Python 帮你打。

6. TaskGroup:结构化并发(3.11)

asyncio.gather 的更安全替代,子任务出错能一起取消:

import asyncio

async def fetch(url: str) -> str:
await asyncio.sleep(0.1)
return f"data from {url}"

async def main() -> None:
async with asyncio.TaskGroup() as tg:
t1 = tg.create_task(fetch("api/a"))
t2 = tg.create_task(fetch("api/b"))
print(t1.result(), t2.result())

asyncio.run(main())
注意

TaskGroup 一旦有任何子任务抛错,会取消其它所有子任务,并抛出 ExceptionGroup。这正是我们想要的行为——并发任务要么一起成功,要么一起失败。以前用 gather 时,一个任务挂了其他任务还在跑,debug 起来很痛苦。

7. ExceptionGroup / except*(3.11)

为并发场景设计的「一组异常一起处理」:

def risky(x: int) -> None:
if x < 0:
raise ValueError(f"负数:{x}")

try:
risky(-1)
risky(-2)
except* ValueError as eg:
for e in eg.exceptions:
print("处理:", e)

这个特性配合 TaskGroup 使用,终于让 Python 的并发错误处理不再是「一团乱麻」。

8. type 语句:定义类型别名(3.12)

TypeAlias 的语法糖,还能定义泛型别名:

type Vector = list[float]
type Matrix[T] = list[list[T]]

def norm(v: Vector) -> float:
return sum(x * x for x in v) ** 0.5

# 泛型别名
m: Matrix[int] = [[1, 2], [3, 4]]

以前定义类型别名要用 Vector = list[float] 加一行注释 # type: TypeAlias,现在直接 type Vector = list[float],又清晰又简洁。

9. pathlib.Path.walk()(3.12)

终于不用再写 os.walk,而且能拿到 dir_entry 做更高效过滤:

from pathlib import Path

root = Path(".")
for dirpath, dirnames, filenames in root.walk():
if "__pycache__" in dirnames:
dirnames.remove("__pycache__") # 剪枝,不再深入
for name in filenames:
if name.endswith(".py"):
print(dirpath / name)
提示

Path.walk() 还支持 top_down=False,自底向上遍历——删空目录时非常有用。

os.path 那套 API 是 20 年前设计的,pathlib 才是现代 Python 处理文件路径的正确方式。

10. Self 类型 & override 装饰器(3.11/3.12)

写「返回自己类型」的方法不再痛苦:

from typing import Self, override

class Shape:
def scale(self, factor: float) -> Self:
return self # 子类调用会自动返回子类类型

class Circle(Shape):
def __init__(self, r: float) -> None:
self.r = r

@override
def scale(self, factor: float) -> Self:
self.r *= factor
return self

@override 会在父类没有同名方法时静态报错,防止手滑写错方法名。这个装饰器帮我避免过至少两次线上 bug。

小结

这 10 个特性里,真正改变我写代码习惯的是三个:match-case(替代 if-elif 链)、X | Y 类型注解(让类型写起来不痛苦)、TaskGroup(让并发代码更安全)。其余几个则是「锦上添花」,但堆在一起,已经让 Python 3.12 读起来像一门全新的语言。

如果你还在用 3.9 写代码,强烈建议升级到 3.12——体验的提升不只是「快一点」,而是「写法完全不同」。