编程学习网 > 编程语言 > Python > Python 的 OOPS,不是把函数塞进 class 就完事了!
2026
06-16

Python 的 OOPS,不是把函数塞进 class 就完事了!


代码里一堆字典来回传,字段名写错一个,跑到线上才炸。

日志大概长这样:

KeyError: 'pay_amount'
import_task=20260604_001
row_no=318

这种问题我第一眼一般不先怪 Python,先看数据是怎么被组织的。很多脚本一开始就几十行,后来越堆越长,最后变成:函数套函数,字典套字典,谁也不知道这个对象到底应该有哪些字段。

Python 里的 OOPS,通常就是 OOPObject-Oriented Programming,面向对象编程。

它不是为了显得高级,也不是为了把简单代码写复杂。它真正有用的地方,是把一堆散落的数据和操作,收拢到一个稳定的东西里。

比如订单导入,很多人一开始会这么写:

def calc_fee(row):
    if row["pay_amount"] <= 0:
        raise ValueError("支付金额不合法")
    return row["pay_amount"] * 0.006

def build_record(row):
    return {
        "order_id": row["order_id"],
        "user_id": row["user_id"],
        "fee": calc_fee(row),
        "status": "WAIT_CHECK"
    }

这代码刚写完没问题。问题在后面。

某天字段从 pay_amount 改成 amount_cent,再加一个退款单判断,再来一个渠道费率,函数就开始到处传参数。这个时候还硬撑,我觉得没必要。

我会先把订单导入行这个东西拎出来:

class ImportOrder:
    def __init__(self, raw: dict):
        self.order_id = raw.get("order_id")
        self.user_id = raw.get("user_id")
        self.amount_cent = int(raw.get("amount_cent", 0))
        self.channel = raw.get("channel", "UNKNOWN")
    def check(self):
        if not self.order_id:
            raise ValueError("order_id 为空")
        if self.amount_cent <= 0:
            raise ValueError(f"订单金额异常{self.order_id}")
    def fee_cent(self):
        rate = 8 if self.channel == "APP" else 10
        return self.amount_cent * rate // 10000
    def to_db_row(self):
        self.check()
        return {
            "order_id": self.order_id,
            "user_id": self.user_id,
            "amount_cent": self.amount_cent,
            "fee_cent": self.fee_cent(),
            "status": "WAIT_CHECK"
        }

这就是最基本的面向对象。

ImportOrder 是类,表示一种东西的模板。

order = ImportOrder(row) 生成出来的就是对象。

属性是对象身上的数据,比如 order_idamount_cent

方法是对象能做的事,比如 check()fee_cent()to_db_row()

这几个概念不用背,写两次业务代码就知道了。对象不是摆设,它得能兜住业务规则。

我比较反感一种写法:

class Order:
    pass

然后外面到处写:

order.status = "A"
order.amount = 100
order.xxx = "随便塞"

这不叫面向对象,这只是换了个皮的字典,还不如字典直接。

面向对象第一个好处,是封装。

金额是否合法,手续费怎么算,入库字段怎么组装,都放在 ImportOrder 里面。外面的导入流程就干净很多:

def import_orders(rows):
    ok_rows = []
    bad_rows = []
    for index, raw in enumerate(rows, start=1):
        try:
            order = ImportOrder(raw)
            ok_rows.append(order.to_db_row())
        except Exception as e:
            bad_rows.append({
                "row_no": index,
                "reason": str(e),
                "raw": raw
            })
    return ok_rows, bad_rows

这段代码读起来就像现场排障时我想看的东西:第几行错了,为什么错,原始数据是什么。不要把所有判断摊在一个大函数里,后面查问题很烦。

再看继承。

继承不是必须用,很多时候组合更舒服。但有些场景它确实顺手,比如不同渠道订单,基础校验一样,手续费不一样。

class BasePayOrder:
    def __init__(self, order_id, amount_cent):
        self.order_id = order_id
        self.amount_cent = amount_cent
    def check_amount(self):
        if self.amount_cent <= 0:
            raise ValueError(f"金额异常{self.order_id}")
    def fee_cent(self):
        raise NotImplementedError

class AppPayOrder(BasePayOrder):
    def fee_cent(self):
        self.check_amount()
        return self.amount_cent * 8 // 10000

class CounterPayOrder(BasePayOrder):
    def fee_cent(self):
        self.check_amount()
        return 0

这里 AppPayOrder  CounterPayOrder 都继承了 BasePayOrder

共同的东西放父类,差异的东西放子类。别一上来就搞五层继承,那种代码我看到也头疼。继承层级越深,排查问题越绕。

再说多态。

多态这词听着像教材,其实就是:不同对象,有同一个方法名,调用方不用关心里面怎么做。

def settle_fee(pay_orders):
    total = 0
    for order in pay_orders:
        total += order.fee_cent()
    return total

这里传进来的是 AppPayOrder 也行,是 CounterPayOrder 也行。只要它有 fee_cent() 方法,这段结算代码就不用改。

这就是 Python 里很常见的鸭子类型。

不问你是不是某个类,只看你能不能干这件事。

但也别乱来。线上代码里我更愿意把对象边界写清楚一点,尤其是导入、结算、库存扣减这种容易出脏数据的地方。Python 太灵活,灵活过头就是事故入口。

还有一个经常被忽略的点:对象要能打印出有用信息。

排查日志时,默认对象打印成这样就很难受:

<__main__.ImportOrder object at 0x1048c1c10>

我一般会补一个 __repr__

class ImportOrder:
    def __init__(self, raw: dict):
        self.order_id = raw.get("order_id")
        self.user_id = raw.get("user_id")
        self.amount_cent = int(raw.get("amount_cent", 0))
        self.channel = raw.get("channel", "UNKNOWN")
    def __repr__(self):
        return (
            f"ImportOrder(order_id={self.order_id}, "
            f"user_id={self.user_id}, amount_cent={self.amount_cent}, "
            f"channel={self.channel})"
        )

这样日志里至少能看:

ImportOrder(order_id=O20260604001, user_id=U7788, amount_cent=12900, channel=APP)

少猜很多东西。

所以 Python OOPS,不是类、对象、继承、多态这几个词背熟了就会了。

它更像一种收拾代码的方式。

数据别散着传,规则别到处飞,异常别吞掉,日志别打印废话。该封装就封装,该拆类就拆类,该用普通函数就用普通函数。Python 不是 Java,不需要每个文件都供一个 class

我自己的判断很简单:一个东西同时有数据、有规则、有生命周期,就值得做成对象。

只有一段计算逻辑,写函数就行。硬套对象,后面一样难维护。

以上就是“Python 的 OOPS,不是把函数塞进 class 就完事了!的详细内容,想要了解更多Python教程欢迎持续关注编程学习网。 

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

Python编程学习

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