Python技术迷

什么是 Python 的 OOPS(面向对象编程)?

订单状态又被改乱了。

日志里一看,很熟悉:

refund failed, order_id=202406120091, status=paid, amount=-30

金额能是负数,已支付订单还能绕过校验直接退款。代码翻出来也不复杂,就是一个大字典到处传:

order = {
"order_id": "202406120091",
"user_id": 18,
"amount": -30,
"status": "paid"
}

这种写法刚开始很爽,字段想加就加,想改就改。

问题也在这里。

谁都能改,哪里都能改,最后就没人知道这个对象到底还干不干净。

Python 的 OOPS,也就是面向对象编程,别一上来就背“封装、继承、多态”。那套话没错,但太干了。放到项目里看,它解决的就是一件事:把一坨到处乱飞的数据,变成一个有边界、有行为、有规矩的对象。

比如订单,不应该只是几个字段。

订单自己就应该知道:什么金额合法,什么状态能退款,什么时候不能改。

我一般会先把这种散落的判断收回来:

classPayOrder:
def__init__(self, order_id, user_id, amount):
if amount <= 0:
raise ValueError(f"订单金额不合法: {amount}")

        self.order_id = order_id
        self.user_id = user_id
        self.amount = amount
        self._status = "created"

    @property
defstatus(self):
return self._status

defmark_paid(self):
if self._status != "created":
raise RuntimeError(f"当前状态不能支付: {self._status}")
        self._status = "paid"

defrefund(self):
if self._status != "paid":
raise RuntimeError(f"当前状态不能退款: {self._status}")
        self._status = "refunded"
return {
"order_id": self.order_id,
"refund_amount": self.amount,
"user_id": self.user_id
        }

这段代码不花哨,但比到处写 if order["status"] == xxx 强多了。

封装不是把字段藏起来显得高级,而是把不该乱改的地方收住。比如 _status 前面这个下划线,不是 Python 强制不让你改,而是在提醒后面接手的人:别手贱直接改它,走方法。

用的时候也直观:

order = PayOrder("202406120091", 18, 99)

order.mark_paid()
refund_bill = order.refund()

print(refund_bill)

状态流转在哪里?在 PayOrder 里面。

金额校验在哪里?也在 PayOrder 里面。

以后退款规则变了,不用全项目搜索 status,先看这个类。排查问题的时候也一样,入口变少了,脑子少炸一半。

面向对象里第二个容易被滥用的是继承。

有些代码一写继承就开始套娃,BaseService、AbstractBaseService、CommonBaseService,看着很有架构感,实际上点进去全是空方法和历史包袱。

我更愿意在规则稳定的时候用继承。

比如系统里有好几种通知方式:短信、邮件、站内信。它们都能发送,但发送细节不一样。

from abc import ABC, abstractmethod

classNoticeSender(ABC):
    @abstractmethod
defsend(self, user_id, content):
pass

classSmsSender(NoticeSender):
defsend(self, user_id, content):
        print(f"[sms] user={user_id}, msg={content[:20]}")

classMailSender(NoticeSender):
defsend(self, user_id, content):
        print(f"[mail] user={user_id}, subject=订单通知")

classInboxSender(NoticeSender):
defsend(self, user_id, content):
        print(f"[inbox] user={user_id}, saved=1")

这里的重点不是 ABC 多高级。

重点是后面的业务代码不用关心你到底是短信还是邮件:

defpush_order_notice(sender: NoticeSender, user_id, order_id):
    content = f"订单 {order_id} 状态已更新"
    sender.send(user_id, content)

senders = [
    SmsSender(),
    MailSender(),
    InboxSender()
]

for sender in senders:
    push_order_notice(sender, 18, "202406120091")

这就是多态。

同一个 send 方法,不同对象有不同实现。业务流程只认“能发送通知的东西”,不关心背后是短信网关、邮件服务,还是数据库插一条站内信。

这地方很适合扩展。

以后要加企业微信通知,不用改 push_order_notice,新写一个类就行:

classWeComSender(NoticeSender):
defsend(self, user_id, content):
        print(f"[wecom] user={user_id}, pushed=true")

这才是面向对象舒服的地方:不是为了少写几行代码,而是为了少改老代码。

不过 Python 写 OOP,有个坑我得多说一句。

别什么都上类。

一个简单的数据清洗脚本,读文件、过滤字段、写结果,几个函数就能搞定,硬写 5 个类,反而碍事。Python 不是 Java,没必要每件事都先 class XxxManager。

我一般这么判断:

如果只是临时处理数据,用函数。

如果数据和规则总是一起出现,用类。

如果多个对象行为一样但实现不同,再考虑抽象和多态。

比如订单这种,有字段,有状态,有行为,还要防止外面乱改,就适合类。通知发送这种,有统一动作,但渠道不同,就适合多态。至于继承,能不用就先别用,组合很多时候更清楚。

Python 的 OOPS 不是把代码写得“像面向对象”,而是让代码里的业务对象站得住。

订单就该有订单的规矩。

通知就该有通知的接口。

别让字典满天飞,别让状态到处改。出问题的时候你会发现,真正难查的不是语法,而是这条业务规则到底散落在哪十几个文件里。