有段 Python 代码我现在看到就想改:
total = 0
for row in orders:
if row["status"] == "paid":
total += row["amount"]
代码没错,跑起来也没毛病。
但这种 for 循环在项目里一多,代码会慢慢变成一种很熟悉的味道:建个变量,循环,判断,再修改变量。
尤其是数据清洗、日志处理、批量校验这类代码,几十行看下来,全是 for。
我现在写 Python,碰到循环一般不会马上敲 for,先看一眼:我到底是在遍历,还是只想得到一个结果?
这两个东西差别挺大。
上面那段代码,我会直接写:
paid_amount = sum(
item["amount"]
for item in orders
if item["status"] == "paid"
)
不是为了少写几行。
而是我真正关心的是“已支付订单金额总和”,不是“怎么把 total 从 0 一点点加上去”。
这种代码后面改起来也轻松。
比如过滤掉退款订单:
paid_amount = sum(
item["amount"]
for item in orders
if item["status"] == "paid"andnot item["refunded"]
)
逻辑基本都在一块,不需要顺着循环去找哪个变量被改了。
我平时做批量校验时更明显。
以前有人会这么写:
invalid_found = False
for record in import_rows:
ifnot record.get("mobile"):
invalid_found = True
break
if invalid_found:
raise ValueError("导入数据存在空手机号")
这种代码我一般直接换成 any():
if any(not row.get("mobile") for row in import_rows):
raise ValueError("导入数据存在空手机号")
如果业务要求是“所有记录都必须通过校验”,那就是 all():
ready = all(
row.get("user_id") and row.get("amount", 0) > 0
for row in import_rows
)
ifnot ready:
raise ValueError("导入数据校验失败")
any() 和 all() 有个地方挺实用:它们会短路。
any() 找到第一个 True 就停,all() 碰到第一个 False 就停。做几万条记录的前置校验时,我更愿意用这种写法,不会傻乎乎把后面的数据全部扫完。
还有一种循环,我见得更多:纯粹为了组装一个新列表。
user_ids = []
for user in users:
if user["enabled"]:
user_ids.append(user["id"])
这种没什么好犹豫的:
user_ids = [
user["id"]
for user in users
if user["enabled"]
]
字典也是一样。
比如接口返回了一批商品,需要按 SKU 建索引,后面查价格:
price_by_sku = {
item["sku"]: item["sale_price"]
for item in products
if item["sale_price"] isnotNone
}
后面直接:
price = price_by_sku.get(target_sku)
不用再为了查一个 SKU 重复扫描整个列表。
这里我比较嫌弃的一种代码,是循环里面不断查列表:
for sku in request_skus:
for item in products:
if item["sku"] == sku:
# ...
break
数据量小时看不出来,数据一上来就开始难受。
我一般先把数据做成索引,再处理:
product_map = {
item["sku"]: item
for item in products
}
matched = [
product_map[sku]
for sku in request_skus
if sku in product_map
]
代码只是少了一层循环,执行方式已经不是一回事了。
再碰到日志处理,我也很少手写循环计数。
比如从一批日志里统计 ERROR:
error_count = sum(
1
for line in log_lines
if" ERROR "in line
)
想拿第一条超时日志:
timeout_line = next(
(line for line in log_lines if"timeout"in line.lower()),
None
)
以前这种需求特别容易写成:
timeout_line = None
for line in log_lines:
if"timeout"in line.lower():
timeout_line = line
break
能跑,但读代码的人得自己脑补一句:“哦,他其实只是想找第一条。”
next() 已经把这个意图写出来了。
不过我不会为了“不写 for”硬上 map()、filter()。
这种:
names = list(map(lambda x: x["name"], users))
我反而觉得不如:
names = [user["name"] for user in users]
Python 代码不是越函数式越高级。lambda 套多了之后,调试的时候一样难受。
还有一种情况更不能硬改:循环里面有明显副作用。
for task in pending_tasks:
try:
push_task(task)
mark_success(task["id"])
except Exception:
logger.exception("push failed, task_id=%s", task["id"])
这里有接口调用、有状态修改、有异常处理。
这种 for 我留着。
你真把它塞进 map(),代码不会更 Python,只会更别扭。
所以我现在所谓的“不再写 Python for 循环”,并不是项目里彻底看不到 for。
而是少写这种东西:
初始化变量 → 循环 → 判断 → append / 累加 / 改状态。
看到这套结构,我通常先试一下 sum()、any()、all()、next()、推导式或者生成器。
能直接描述结果,就别把执行步骤一行一行摊开。
for 当然没问题。
问题是有些代码明明只想问一句“有没有”“一共有多少”“找第一个”“转换成什么”,最后却写成十几行循环。
这种 for,我现在基本不留。