编程学习网 > 编程语言 > Python > 13 个可能未使用过的 Python 特性!
2026
10-20

13 个可能未使用过的 Python 特性!

Python 写久了以后,有个挺明显的现象。 代码能写,需求也能做,但翻来覆去还是那几个东西:

for
if
dict
list
try
except

碰到批量处理,就手搓循环;碰到字典取值,就一层层判断;碰到资源释放,继续 try...finally。

代码当然没错。

但有些代码我现在回头看,第一反应就是:当时写得有点笨。

Python 其实藏了不少不算冷门、但很多人一直没真正用起来的特性。下面这 13 个,我挑的都是工作里真能塞进去的,不搞那些为了炫技才写出来的语法。


先看一个我现在挺常用的东西:enumerate()。

以前处理导入文件,经常能看到这种代码:

row_no = 0
for row in rows:
    row_no += 1
    if not row.get("phone"):
        bad_rows.append({
            "row": row_no,
            "reason": "phone empty"
        })

自己维护下标没什么必要。

for row_no, row in enumerate(rows, start=1):
    if not row.get("phone"):
        bad_rows.append({
            "row": row_no,
            "reason": "phone empty"
        })

start=1 这个参数尤其适合 Excel、CSV 这种场景。

业务人员说“第 37 行数据有问题”,你日志里最好也打印第 37 行,别打印个数组下标 36,然后两边互相怀疑。

再比如 zip()。

有次处理一批接口返回字段,需要把字段名和结果拼起来。

笨写法一般这样:

payload = {}
for i in range(len(columns)):
    payload[columns[i]] = values[i]

我现在一般直接:

payload = dict(zip(columns, values))

当然,这地方我会多看一眼两个列表长度。

Python 3.10 以后,zip() 可以直接加 strict=True:

payload = dict(zip(columns, values, strict=True))

长度对不上,直接报错。

这个比静默截断靠谱多了。

尤其是批量同步字段这种代码,静默少一个值,比直接挂掉更难查。

第三个是字典的 setdefault()。

做日志统计时,经常需要按照接口分组:

stats = {}
for item in access_logs:
    uri = item["uri"]
    if uri not in stats:
        stats[uri] = []
    stats[uri].append(item["cost"])

可以压成:

stats = {}
for item in access_logs:
    stats.setdefault(item["uri"], []).append(item["cost"])

不过这里顺手说一句。

如果逻辑复杂,我反而更喜欢 defaultdict:

from collections import defaultdict
cost_bucket = defaultdict(list)
for item in access_logs:
    cost_bucket[item["uri"]].append(item["cost"])

别为了少两行代码硬塞 setdefault()。

代码短不等于代码好看。

第四个是 collections.Counter。

我查日志的时候用得不少。

比如已经把一分钟内的错误日志提出来了,想看哪个错误最多:

errors = [
    "timeout",
    "timeout",
    "connection reset",
    "timeout",
    "bad gateway",
    "connection reset",
]

不用自己写字典计数:

from collections import Counter
count = Counter(errors)
print(count.most_common(3))

结果类似:

[
    ("timeout", 3),
    ("connection reset", 2),
    ("bad gateway", 1)
]

线上排障的时候,这种东西比肉眼翻几千行日志省事。

我甚至会直接把网关日志里的 URI、状态码、异常类型各跑一次 Counter,先看分布,再决定往哪个方向查。

第五个是 any() 和 all()。

这两个函数看着简单,但不少业务代码还在手动维护布尔变量。

比如批量导入用户,只要有一条手机号为空就拒绝:

invalid = False
for row in rows:
    if not row.get("phone"):
        invalid = True
        break
if invalid:
    raise ValueError("存在空手机号")

直接:

if any(not row.get("phone") for row in rows):
    raise ValueError("存在空手机号")

反过来,如果要求全部任务都执行成功:

if all(task["status"] == "DONE" for task in tasks):
    mark_batch_finished(batch_id)

这种代码读起来更接近业务本身。

我看代码时比较烦这种东西:

flag = True

然后往下翻几十行,途中被改三四次。

最后你还得重新推一遍这个 flag 到底代表什么。

第六个,next()。

查列表里的第一个匹配项,很多人还是这么写:

target = None
for job in jobs:
    if job["job_id"] == wanted_id:
        target = job
        break

可以直接:

target = next(
    (job for job in jobs if job["job_id"] == wanted_id),
    None
)

这个写法我经常用在接口返回结果里找某条数据。

但别写成一条两百字符的大长句。

条件复杂了,老老实实拆开。

第七个是 for...else。

这个语法不少 Python 开发都见过,但真用过的人没想象中多。

比如查一批机器,找到异常节点就退出:

for node in nodes:
    if node["load"] > 0.9:
        print("找到高负载节点:", node["host"])
        break
else:
    print("本轮没有发现高负载节点")

注意这个 else 不是跟 if 配的。

它的意思是:

循环没有被 break 打断时,执行 else。

这个用来写搜索、校验挺顺手。

比如:

for row in rows:
    if row["order_no"] == order_no:
        handle(row)
        break
else:
    raise LookupError(f"order not found: {order_no}")

第一次看到确实有点别扭。

但在这种“找到了就 break,没找到就处理”的代码里,比额外维护一个 found 变量干净。

第八个是解包。

这个其实远不止:

a, b = b, a

我更常用的是星号解包。

比如接口路径:

parts = [
    "api",
    "v2",
    "orders",
    "83921"
]

可以:

root, version, *resource = parts
print(root)
print(version)
print(resource)

还有处理 CSV:

user_id, name, *extra = row

后面字段再增加几个,也不至于马上把解析代码干崩。

函数传参同样能解包:

query = {
    "user_id": 1024,
    "status": "ACTIVE"
}
load_users(**query)

配置驱动型代码里挺好用。

第九个是字典合并运算符 |。

以前合并配置经常这么写:

config = default_config.copy()
config.update(env_config)
config.update(user_config)

现在可以直接:

config = default_config | env_config | user_config

后面的值覆盖前面的值。

比如:

base = {
    "timeout": 3,
    "retry": 2,
    "verify_ssl": True
}
prod = {
    "timeout": 8,
    "retry": 4
}
config = base | prod

这种特别适合配置覆盖。

我一般会按照:

默认配置
环境配置
运行参数

这个顺序合并。

顺序别写反。

这类 Bug 最恶心的地方就是代码完全不报错,只是你以为配置生效了,其实被后面覆盖了。

第十个是海象运算符 :=。

这个符号第一次看到,很容易觉得 Python 开始往奇怪方向发展了。

但用对地方其实挺舒服。

比如循环读取文件:

while chunk := file.read(8192):
    process(chunk)

不用再写:

while True:
    chunk = file.read(8192)
    if not chunk:
        break
    process(chunk)

再比如处理接口结果:

if token := response.get("next_token"):
    fetch_next_page(token)

不过这个东西很容易被玩坏。

像这种我就不喜欢:

if (data := get_data()) and (count := len(data)) > 10:
    ...

再继续套两层,代码基本就开始往谜语方向走了。

海象运算符适合减少一次无意义赋值,不适合拿来证明自己会 Python。

第十一个是 functools.lru_cache。

有些函数参数一样,结果也一样,但计算又比较贵,可以直接缓存。

比如读取某个固定服务的配置:

from functools import lru_cache

@lru_cache(maxsize=128)
def load_service_rule(service_name):
    return parse_rule_file(
        f"/etc/app/rules/{service_name}.json"
    )

第一次调用会真正读取和解析。

后面相同参数直接走缓存。

我用这个时有个习惯:先确认函数是不是“适合缓存”。

如果函数里面查的是实时数据库:

@lru_cache
def get_order_status(order_id):
    ...

这种我第一眼就不太信。

订单状态都变了,你还把旧结果缓存函数里,后面排查起来够喝一壶。

缓存不是加个装饰器就完事,先看数据生命周期。

第十二个是 contextlib。

大家熟悉:

with open(...) as f:

其实自己的资源也可以做成 with。

比如临时切换工作目录:

from contextlib import contextmanager
from pathlib import Path
import os

@contextmanager
def workdir(path):
    old = Path.cwd()
    os.chdir(path)
    try:
        yield
    finally:
        os.chdir(old)

使用:

with workdir("/data/import"):
    run_import()

我比较喜欢这种写法的原因,不是少代码。

而是“清理动作”被固定住了。

文件关闭、锁释放、临时目录清理、数据库连接回收,这种东西最怕业务中途 return,或者异常后漏执行。

能塞进上下文管理器的,我一般不靠开发人员“记得释放”。

最后一个,match...case。

Python 3.10 之后支持结构化模式匹配。

如果只是这种代码:

if status == 1:
    ...
elif status == 2:
    ...

我觉得没必要为了新语法硬改。

它真正好用的是拆结构。

比如消息消费:

def handle_event(event):
    match event:
        case {
            "type": "order_paid",
            "order_id": order_id,
            "amount": amount
        }:
            mark_paid(order_id, amount)
        case {
            "type": "order_cancelled",
            "order_id": order_id
        }:
            cancel_order(order_id)
        case {
            "type": "inventory_changed",
            "sku": sku,
            "delta": delta
        }:
            change_stock(sku, delta)
        case _:
            raise ValueError(
                f"unsupported event: {event}"
            )

这种代码比连续写一堆:

event.get(...)

舒服很多。

特别是 MQ、Webhook、协议解析这种场景,本来就在根据消息结构分流,match 放进去还挺顺。

但我还是那个习惯:语法可以新,判断不能绕。

代码最后是给人维护的,不是给 Python 解释器欣赏的。

这 13 个东西里,我平时用得最多的其实不是最花哨的 match 和海象运算符,反而是:

enumerate、zip、Counter、any/all、解包、上下文管理器。

原因也简单。

它们解决的都是每天会碰到的小破事。

批量数据怎么带行号,两个列表怎么安全拼接,日志怎么快速统计,资源怎么保证释放,配置怎么覆盖。

Python 真正舒服的地方,不是能把十行代码压成一行。

而是有些你原本需要自己维护的状态,它已经帮你处理掉了。

这种代码我现在看到:

index = 0
found = False
result = {}

后面再跟几个循环,我一般都会多看两眼。

不是一定写错了。

只是八成还有地方能收拾。

以上就是“13 个可能未使用过的 Python 特性!”的详细内容,想要了解更多Python教程欢迎持续关注编程学习网。 

扫码二维码 获取免费视频学习资料

Python编程学习

查 看2022高级编程视频教程免费获取