user.name 打印出来是“张三”,User.name 却不一定有东西。
这个问题别急着背概念,类和对象的区别,我一般不从“类是模板,对象是实例”开始讲。那句话没错,但太滑了,滑到最后还是不会用。
先看一段现场里更像会出现的代码:
classDevice:
category = "scanner"
def__init__(self, sn, owner):
self.sn = sn
self.owner = owner
self.online = False
defmark_online(self):
self.online = True
defshow(self):
returnf"{self.sn} / {self.owner} / {self.online}"
d1 = Device("A1001", "factory-01")
d2 = Device("A1002", "factory-02")
d1.mark_online()
print(d1.show())
print(d2.show())
print(Device.category)
这段代码里,Device 是类,d1、d2 是对象。
但这里真正要盯住的不是这句话,而是:d1 和 d2 各自有自己的 sn、owner、online。d1.mark_online() 改的是 d1 自己,不会把 d2 顺手也改了。
这就是对象最有用的地方:它带着自己的状态。
类更像一套规则。它规定一个设备对象创建出来应该有哪些字段,能执行哪些动作。对象才是真正在内存里干活的那个东西。
我刚学 Python 那会儿,最容易混的是这一句:
d1 = Device("A1001", "factory-01")
很多人以为这是“调用了类”。更准确一点说,是用 Device 这个类创建了一个对象,然后把这个对象交给变量 d1 引用。
可以用 id() 看一下:
d1 = Device("A1001", "factory-01")
d2 = Device("A1001", "factory-01")
print(id(d1))
print(id(d2))
print(d1 is d2)
哪怕两个对象里面的数据一样,它俩也不是一个东西。线上排问题时这点很重要,尤其是你把对象塞进缓存、列表、队列的时候,别看到字段一样就以为是同一个对象。
再看一个坑,类变量和对象变量。
classJob:
retry_limit = 3
def__init__(self, job_id):
self.job_id = job_id
self.retry_count = 0
j1 = Job("sync-user")
j2 = Job("sync-order")
j1.retry_count += 1
print(j1.retry_count) # 1
print(j2.retry_count) # 0
print(Job.retry_limit) # 3
retry_limit 写在类下面,不在 __init__ 里,它是类变量。所有对象默认都能读到它。
retry_count 写在 self 上,它是对象自己的变量。
这地方我一般会多看一眼,因为有些 bug 就是把本该属于对象的数据写成了类变量。
比如下面这个就不太对劲:
classImportTask:
failed_rows = []
def__init__(self, filename):
self.filename = filename
defadd_error(self, row_no):
self.failed_rows.append(row_no)
t1 = ImportTask("a.xlsx")
t2 = ImportTask("b.xlsx")
t1.add_error(3)
print(t2.failed_rows)
你以为 t2.failed_rows 是空的,结果里面有 3。
这不是 Python 抽风,是你把列表放到了类上。failed_rows 成了所有对象共享的一份数据。批量导入、任务状态、错误明细这种东西,基本都不该这么写。
改成这样才像正常业务代码:
classImportTask:
def__init__(self, filename):
self.filename = filename
self.failed_rows = []
defadd_error(self, row_no, reason):
self.failed_rows.append({
"row": row_no,
"reason": reason
})
task = ImportTask("user_2026_06.xlsx")
task.add_error(17, "mobile is empty")
print(task.failed_rows)
这里的 failed_rows 是每个对象自己的。你起十个导入任务,它们不会互相串数据。
再说 self。
self 不是关键字,但最好别改名字。它代表当前这个对象。
classAccount:
def__init__(self, account_id, balance):
self.account_id = account_id
self.balance = balance
deffreeze(self):
print(f"freeze account: {self.account_id}")
当你写:
acc = Account("U7788", 200)
acc.freeze()
Python 会把 acc 自动传给 freeze 里的 self。所以 freeze 才知道自己冻结的是哪个账户。
这就是类方法看起来少传一个参数的原因。不是没有传,是 Python 帮你传了。
类和对象的区别,落到写代码上,其实就这几个判断:
类负责定义结构和行为。 对象负责保存具体数据。 类只有一份,对象可以有很多个。 写在 self 上的,通常是对象自己的。 直接写在类上的,要小心是不是会被所有对象共享。
我平时看一段 Python 面向对象代码,会先看 __init__。因为对象的关键状态基本都在那里。再看方法里改了哪些 self.xxx。如果一个方法疯狂改类变量,我就会不太放心,尤其是 Web 服务、异步任务、批处理脚本这种长时间运行的程序。
最后再压一句。
类不是为了显得高级,对象也不是为了把简单代码写复杂。它们真正有用的地方,是把“数据”和“操作这份数据的动作”放在一起。订单有订单的状态,设备有设备的状态,任务有任务的失败记录。
代码跑久了,你会发现,能不能分清类和对象,不是考试题,是排 bug 时少绕几圈。