线上脚本一跑就炸:
TypeError: OrderCleaner.mark_paid() missing 1 required positional argument: 'self'
这种报错我一般先不看业务逻辑,先看方法是怎么调用的。八成不是订单状态错了,也不是数据库字段错了,是你把“类里的方法”当成普通函数直接调了。
看一段很常见的代码:
classOrderCleaner:
defmark_paid(self, order_id):
print(f"订单 {order_id} 标记为已支付")
OrderCleaner.mark_paid("A1024")
这段代码看着没毛病,实际会报错。
原因就在 self。
mark_paid 写在类里面,并且第一个参数是 self,它就是一个实例方法。实例方法不是直接给类调用的,它要先有一个对象。
应该这样写:
classOrderCleaner:
defmark_paid(self, order_id):
print(f"订单 {order_id} 标记为已支付")
cleaner = OrderCleaner()
cleaner.mark_paid("A1024")
这里 cleaner.mark_paid("A1024") 表面只传了一个参数,实际 Python 偷偷帮你传了两个:
OrderCleaner.mark_paid(cleaner, "A1024")
第一个参数 cleaner,就落到了 self 上。
所以 self 不是玄学,也不是装饰用的。它就是当前这个对象自己。
我刚开始看 Python 类的时候,也觉得这个写法别扭。Java 里是 this,Python 非要手写一个 self。后来写多了就知道,这东西虽然啰嗦,但它很直白:这个方法到底在操作哪个对象,一眼能看出来。
比如下面这个例子:
classUserLoginStat:
def__init__(self, user_id):
self.user_id = user_id
self.fail_count = 0
defrecord_fail(self):
self.fail_count += 1
print(f"user={self.user_id}, fail_count={self.fail_count}")
u1 = UserLoginStat("U1001")
u2 = UserLoginStat("U2002")
u1.record_fail()
u1.record_fail()
u2.record_fail()
输出大概是:
user=U1001, fail_count=1
user=U1001, fail_count=2
user=U2002, fail_count=1
这里有个点别看漏了。
u1 和 u2 是两个对象。它们都调用 record_fail,但里面的 self 不一样。
u1.record_fail() 里的 self 指向 u1。
u2.record_fail() 里的 self 指向 u2。
所以 u1.fail_count 加了两次,不会影响 u2.fail_count。
这才是 self 真正干的事:把数据绑到当前对象上。
再看一个容易写坏的地方。
classExportTask:
file_count = 0
defadd_file(self, filename):
self.file_count += 1
print(f"加入文件: {filename}, 当前数量: {self.file_count}")
task_a = ExportTask()
task_b = ExportTask()
task_a.add_file("2026-订单.csv")
task_b.add_file("2026-退款.csv")
这里 file_count 写在类下面,看着像大家共享一个计数。可一旦你写了:
self.file_count += 1
Python 会优先在当前对象上创建一个 file_count。也就是说,task_a 有自己的,task_b 也有自己的。
如果你真想让所有对象共享一个计数,就别用 self 改它,要用类名:
classExportTask:
total_file_count = 0
defadd_file(self, filename):
ExportTask.total_file_count += 1
print(f"加入文件: {filename}, 总文件数: {ExportTask.total_file_count}")
这个坑在线上脚本里挺常见,尤其是写批处理、导入导出、日志统计的时候。看起来只是一个变量放错位置,跑一批数据之后,统计数全乱。
还有人会问,self 能不能换成别的名字?
能。
classPriceChecker:
defcheck(this, sku, price):
this.sku = sku
this.price = price
这代码能跑。
但我一般不这么写。不是不能,是没必要。团队里别人看到第一个参数不是 self,第一反应肯定是停一下。代码里最怕这种“能跑但膈应”的写法,尤其排线上问题的时候,别人没空欣赏个性。
还有一种情况,方法里根本没用到对象数据,那就别硬写实例方法。
比如:
classAmountParser:
defparse_cent(self, amount_text):
return int(float(amount_text) * 100)
这个方法没有用 self,说明它不关心当前对象。那我会改成静态方法:
classAmountParser:
@staticmethod
defparse_cent(amount_text):
return int(float(amount_text) * 100)
cent = AmountParser.parse_cent("19.80")
print(cent)
这样调用更干净,也不会让人误以为它依赖对象状态。
判断一个方法要不要写 self,我一般就看一句话:这个方法需不需要访问当前对象里的数据?
需要,比如用户 ID、连接配置、失败次数、文件路径,那就写 self。
不需要,只是做格式转换、金额换算、字段清洗,那就考虑 staticmethod。
self 不复杂,别背概念。看到它,就把它当成“当前这个对象”。
对象调用方法时,Python 会自动把对象自己塞给 self。
类直接调用实例方法时,你就得自己传对象。
这块想明白了,Python 类里一半看着怪的代码,都顺眼了。