page contents

别再用 utils.py 了!这 3 个设计模式让你的 Python 代码更清晰

今天往里塞一个 format_time(),明天塞一个 send_sms(),后天再补个 check_permission()。过不了多久,这文件就像公司楼下五金店,啥都能买到,代价是你永远不知道东西该去哪找,也不知道改一行会不会把别处带崩。

attachments-2026-04-FFlgVcan69d7025c74479.png项目里一旦出现 utils.py,我第一反应 usually 不是“方便”,而是“后面多半要烂”。

今天往里塞一个 format_time(),明天塞一个 send_sms(),后天再补个 check_permission()。过不了多久,这文件就像公司楼下五金店,啥都能买到,代价是你永远不知道东西该去哪找,也不知道改一行会不会把别处带崩。

我见过最离谱的一个 utils.py,两千多行,里面同时有 Excel 导出、订单状态转换、Redis key 拼装、签名校验。出问题时,堆栈里全是 utils.xxx,看着就烦。这种代码不是不能跑,是排查起来很脏。

真要拆,不用上来就搞大重构。先把那种“塞一起纯图省事”的代码,按职责掰开。下面这 3 个模式,够用了。

1)策略模式:别再写一串 if else 了

最常见的烂法,是不同渠道、不同类型、不同规则,硬堆在一个函数里。

def calc_discount(order_type: str, amount: int) -> int:

    if order_type == "vip":

        return int(amount * 0.8)

    if order_type == "new_user":

        return 30 if amount > 200 else 10

    if order_type == "festival":

        return min(50, int(amount * 0.15))

    return 0

刚开始三种还行。等运营再加“直播间专享”“企业团购”“会员日叠券”,这个函数就开始发臭了。

这种地方我一般直接上策略模式,不和 if else 纠缠。

from abc import ABC, abstractmethod

class DiscountStrategy(ABC):

    @abstractmethod

    def calc(self, amount: int) -> int:

        pass


class VipDiscount(DiscountStrategy):

    def calc(self, amount: int) -> int:

        return int(amount * 0.8)

class NewUserDiscount(DiscountStrategy):

    def calc(self, amount: int) -> int:

        return 30 if amount > 200 else 10

class FestivalDiscount(DiscountStrategy):

    def calc(self, amount: int) -> int:

        return min(50, int(amount * 0.15))

STRATEGIES = {

    "vip": VipDiscount(),

    "new_user": NewUserDiscount(),

    "festival": FestivalDiscount(),

}

def calc_discount(order_type: str, amount: int) -> int:

    return STRATEGIES.get(order_type, FestivalDiscount()).calc(amount)

好处不是“高级”,是后面加规则时你不用回头碰老代码。线上这类逻辑最怕改旧分支,尤其结算、优惠、风控,动一下就容易带出历史 bug。

2)工厂模式:别把对象创建到处写

还有一种 utils.py 味道也很重:各种客户端初始化到处复制。

def send_message(channel: str, content: str):

    if channel == "sms":

        client = SmsClient(api_key="xxx")

        client.send(content)

    elif channel == "email":

        client = EmailClient(host="smtp.xxx.com")

        client.send(content)

这种代码刚写出来很顺手,后面最麻烦。因为创建对象的细节散得到处都是,改个配置、补个重试、加个超时,要全项目搜。

工厂模式不复杂,说白了就是把“怎么创建”收口。

class SmsClient:

    def __init__(self, api_key: str):

        self.api_key = api_key


    def send(self, content: str):

        print(f"sms sent: {content}")


class EmailClient:

    def __init__(self, host: str):

        self.host = host


    def send(self, content: str):

        print(f"email sent: {content}")

class NotifyFactory:

    @staticmethod

    def create(channel: str):

        if channel == "sms":

            return SmsClient(api_key="sms-prod-key")

        if channel == "email":

            return EmailClient(host="smtp.internal")

        raise ValueError(f"unknown channel: {channel}")

def send_message(channel: str, content: str):

    client = NotifyFactory.create(channel)

    client.send(content)

这地方值钱的是“统一出口”。比如以后短信要切供应商,我会优先改工厂,不会去全局抓 SmsClient(...)。排障的时候也一样,先看工厂吐出来的是谁,比满项目翻要省事。

3)模板方法:流程别复制,留钩子就行

还有一类烂代码,看着不像 utils.py,其实本质一样:流程复制。

导入 CSV 一份,导入 Excel 一份,导入接口数据再来一份。每份代码都是“读数据、校验、清洗、入库”,只是细节不同。

这种我一般不让大家继续 copy 了,直接抽流程骨架。

from abc import ABC, abstractmethod

class BaseImporter(ABC):

    def run(self, rows: list[dict]):

        cleaned = self.clean(rows)

        self.validate(cleaned)

        self.save(cleaned)


    @abstractmethod

    def clean(self, rows: list[dict]) -> list[dict]:

        pass


    @abstractmethod

    def validate(self, rows: list[dict]):

        pass


    def save(self, rows: list[dict]):

        print(f"save {len(rows)} rows")


class UserImporter(BaseImporter):

    def clean(self, rows: list[dict]) -> list[dict]:

        return [{"uid": r["uid"].strip(), "name": r["name"].strip()} for r in rows]


    def validate(self, rows: list[dict]):

        for r in rows:

            if not r["uid"]:

                raise ValueError("uid empty")

模板方法的意思不是炫技,就是把稳定流程钉住,把变化点留给子类。这样你后面看代码,一眼就知道流程没跑偏,问题大概率在 clean() 还是 validate(),定位很快。

utils.py 最大的问题,不是文件名丑,是它默认接受“无边界增长”。什么都能塞,最后就什么都说不清。

所以我现在看 Python 项目,先不看框架,先看有没有一坨公共函数到处乱飞。真有,八成已经开始欠债了。

能按规则切换的,用策略模式。 能把创建逻辑收口的,用工厂模式。 能把公共流程压住的,用模板方法。

别再往 utils.py 里塞了。那不是复用,那是堆积。

更多相关技术内容咨询欢迎前往并持续关注好学星城论坛了解详情。

想高效系统的学习Python编程语言,推荐大家关注一个微信公众号:Python编程学习圈。每天分享行业资讯、技术干货供大家阅读,关注即可免费领取整套Python入门到进阶的学习资料以及教程,感兴趣的小伙伴赶紧行动起来吧。

attachments-2022-05-rLS4AIF8628ee5f3b7e12.jpg

你可能感兴趣的文章

相关问题

0 条评论

请先 登录 后评论
Pack
Pack

1939 篇文章

作家榜 »

  1. 轩辕小不懂 2403 文章
  2. 小柒 2228 文章
  3. Pack 1939 文章
  4. Nen 576 文章
  5. 王昭君 209 文章
  6. 文双 71 文章
  7. 小威 64 文章
  8. Cara 36 文章