Python技术迷

还在用 if-else 堆代码?这 10 个 Python 写法能救一下

一坨 if-else 最烦的地方,不是丑。 是你改第三次的时候,已经不敢确定上一层条件还成不成立。

我见过这种代码,订单状态加一个“退款中”,优惠券再加一个“部分核销”,最后整个函数像套娃。你第一眼看着还能跑,第二眼就想打日志。

比如这种:

defcalc_fee(order):
if order["type"] == "vip":
if order["city"] == "sh":
return order["amount"] * 0.88
else:
return order["amount"] * 0.9
elif order["type"] == "new_user":
return order["amount"] - 20
elif order["type"] == "staff":
return0
else:
return order["amount"]

这种代码不是不能写,业务刚起步的时候我也写。问题是它特别容易长歪。

换成字典分发,至少先把“判断”和“动作”拆开:

defvip_fee(order):
    rate = 0.88if order["city"] == "sh"else0.9
return order["amount"] * rate

defnew_user_fee(order):
return max(order["amount"] - 20, 0)

defstaff_fee(order):
return0

FEE_RULES = {
"vip": vip_fee,
"new_user": new_user_fee,
"staff": staff_fee,
}

defcalc_fee(order):
    handler = FEE_RULES.get(order["type"], lambda x: x["amount"])
return handler(order)

这地方我一般不追求什么“设计模式”,先把函数拆出来。后面规则多了,至少能单独测。

第二个,能提前返回就提前返回。不要为了显得“结构完整”,把所有逻辑都塞在一个大缩进里。

defsubmit_refund(order):
if order isNone:
return"order_empty"

if order["status"] != "paid":
return"status_not_allowed"

if order["refund_amount"] <= 0:
return"bad_amount"

return do_refund(order)

这种写法土,但是稳。线上排问题时,我更愿意看这种一眼能断掉的代码。

第三个,多个条件别硬拼 and、or。尤其是权限、状态、渠道这种东西。

ALLOW_STATUS = {"paid", "part_refunded"}
ALLOW_CHANNEL = {"app", "miniapp"}

defcan_refund(order):
return (
        order["status"] in ALLOW_STATUS
and order["channel"] in ALLOW_CHANNEL
andnot order.get("risk_locked")
    )

别小看这个集合。以后产品说“PC 端也能退”,你改一行,不用进条件地狱里翻半天。

第四个,遇到多个字段校验,可以用校验器列表。这个写法我在导入 Excel、批量清洗接口参数时用得挺多。

defcheck_mobile(row):
returnNoneif row.get("mobile", "").isdigit() else"手机号格式不对"

defcheck_amount(row):
returnNoneif float(row.get("amount", 0)) > 0else"金额不能小于等于0"

defcheck_name(row):
returnNoneif row.get("name") else"姓名为空"

CHECKERS = [check_name, check_mobile, check_amount]

defvalidate_row(row):
    errors = []
for checker in CHECKERS:
        msg = checker(row)
if msg:
            errors.append(msg)
return errors

这样加规则不会破坏原来的判断。导入失败时还能直接把错误写回去。

第五个,match-case 不是炫技,适合处理结构比较固定的事件。

defhandle_event(event):
    match event:
        case {"type": "pay_success", "order_id": oid, "amount": amount}:
return sync_pay_result(oid, amount)

        case {"type": "refund_done", "order_id": oid}:
return close_refund_ticket(oid)

        case {"type": "user_locked", "user_id": uid}:
return clear_user_token(uid)

        case _:
return write_unknown_event(event)

我不建议所有地方都用它。但消费 MQ、处理 webhook、解析回调消息,这玩意比一堆 if event["type"] == xxx 清楚。

第六个,别把默认值判断写得太散。dict.get()、setdefault() 这种小东西,能少很多脏判断。

defcollect_by_shop(orders):
    bucket = {}

for order in orders:
        shop_id = order["shop_id"]
        bucket.setdefault(shop_id, []).append(order["order_id"])

return bucket

当然,这个地方也可以用 defaultdict:

from collections import defaultdict

defcollect_pay_amount(orders):
    amount_map = defaultdict(float)

for order in orders:
if order["status"] == "paid":
            amount_map[order["shop_id"]] += order["pay_amount"]

return dict(amount_map)

这种代码没什么花活,但比每次先判断 key 在不在舒服。

第七个,别在循环里写一堆标记变量。any() 和 all() 很适合处理“有没有”“是不是都满足”。

defneed_manual_review(order):
    hit_risk_word = any(
        word in order["remark"]
for word in ("线下转账", "补差价", "私聊")
    )

    all_goods_ready = all(
        item["stock_status"] == "ready"
for item in order["items"]
    )

return hit_risk_word ornot all_goods_ready

这里可读性比你写 flag = False 然后循环里改来改去强多了。

第八个,状态值不要散落在字符串里。至少用 Enum 管一下。

from enum import StrEnum

classOrderStatus(StrEnum):
    CREATED = "created"
    PAID = "paid"
    CLOSED = "closed"
    REFUNDING = "refunding"

defcan_ship(order):
return order["status"] == OrderStatus.PAID

字符串最坑的是拼错了也不一定马上炸。refunding 写成 refound,测试数据没覆盖,线上就开始演你。

第九个,多个相似流程,别复制函数。把变化的地方作为参数塞进去。

defexport_rows(rows, pick_fields):
    result = []
for row in rows:
        result.append({name: getter(row) for name, getter in pick_fields.items()})
return result

ORDER_EXPORT_FIELDS = {
"订单号": lambda x: x["order_no"],
"用户": lambda x: x["buyer_name"],
"实付": lambda x: round(x["pay_amount"], 2),
}

rows = export_rows(order_list, ORDER_EXPORT_FIELDS)

这类代码在后台导出里太常见了。每个导出都复制一份循环,后面改字段格式能改到怀疑人生。

第十个,实在分支很多,就别硬装优雅,直接建规则表。

RULES = [
    {
"name": "大额订单",
"hit": lambda o: o["pay_amount"] >= 5000,
"tag": "big_order",
    },
    {
"name": "新用户高频下单",
"hit": lambda o: o["user_level"] == "new"and o["order_count_today"] >= 3,
"tag": "new_user_risk",
    },
    {
"name": "地址异常",
"hit": lambda o: o.get("address_score", 100) < 60,
"tag": "bad_address",
    },
]

defmark_order(order):
    tags = []

for rule in RULES:
if rule["hit"](order):
            tags.append(rule["tag"])

return tags

这种写法不一定“高级”,但它抗改。规则多了可以放配置,甚至放数据库。最关键的是,业务说“加一条判断”,你不用拆半天老代码。

Python 里不是不能写 if-else,该写还是写。 但有些代码一眼看过去就是会长胖的,那就早点拆。

我自己的习惯很简单: 一层判断能说清楚,就直接写。 两层开始犹豫。 三层还往下套,我一般就停一下,先想想是不是该换个写法了。