
代码里一堆字典来回传,字段名写错一个,跑到线上才炸。
日志大概长这样:
KeyError: 'pay_amount'
import_task=20260604_001
row_no=318
这种问题我第一眼一般不先怪 Python,先看数据是怎么被组织的。很多脚本一开始就几十行,后来越堆越长,最后变成:函数套函数,字典套字典,谁也不知道这个对象到底应该有哪些字段。
Python 里的 OOPS,通常就是 OOP,Object-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_id、amount_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教程欢迎持续关注编程学习网。
扫码二维码 获取免费视频学习资料

- 本文固定链接: http://www.phpxs.com/post/14236/
- 转载请注明:转载必须在正文中标注并保留原文链接
- 扫码: 扫上方二维码获取免费视频资料