类与对象 —— 属性和方法
普通课约 60 分钟
本课导读
上一课你见识了类的好,今天把语法正面讲透:class 怎么写、__init__ 在什么时候干什么、self 到底是谁(本课最大的拦路虎,一个类比放倒它)、属性和方法怎么配合。学完你能独立写出一个五脏俱全的类,并让 print 出来的对象说人话。预计 60 分钟。
最小的类,逐行拆
健身教练大壮要管理学员,从最小的类开始:
逐行说清:
- ① class 定义模具。类名用首字母大写的驼峰(
Trainee、TodoList)——这是社区约定,正好和蛇形的变量/函数区分开 - ②
__init__是出厂设置:名字怪(前后双下划线),身份特殊——你写Trainee("小雨", 55)时它自动执行,不用手动喊 - ③
self.name = name:把收到的参数存到对象身上,成为属性。存了,这个对象以后随时能用;不存,函数一结束参数就没了(局部变量,上单元讲过) - ④⑤⑥⑦ 造和用——注意造对象时不用传 self,下面马上解释
self 到底是谁
这是本课的大 boss,一个类比拿下:
提示
self 就是"我"这个代词。 模具(类)是给所有产品写的说明书,说明书里不能写死"小雨的体重",只能写"我的体重"(self.weight)。每个对象照着说明书行动时,"我"自动指向它自己——小雨的 report 里 self 是小雨,大壮的 report 里 self 是大壮。
最后一行是"self 之谜"的官方答案:对象.方法() 时,Python 自动把点前面的对象塞给第一个参数 self。所以定义时写 self,调用时不用传——不是少了个参数,是 Python 替你传了。
说明
两个高频报错,现在就认识:忘写 self(def report():)→ 调用时 TypeError: report() takes 0 positional arguments but 1 was given——那个"多出来的 1"就是 Python 硬塞的对象本尊;方法里忘了 self.(直接写 name)→ NameError——不带 self 的是局部变量,不是属性。
属性会变:对象是活的
属性不是一次定终身——方法可以随时修改它,这正是"对象有状态"的意思:
注意三件事:
self.sessions = 0——属性可以在__init__里直接给初值,不必都来自参数(回想上一课 Task 的self.done = False)- 方法能读属性也能改属性:
train每次都在更新 self 身上的数据 - 方法照常支持上单元的功夫:默认参数
hours=1、关键字调用hours=2——类里的方法就是函数,规矩全通用
规范提醒:把一个对象的全部属性都在 __init__ 里声明齐(哪怕先给个 0 或 None)。技术上你可以事后从外面 ada.new_attr = 1 随手加属性,但那会让"这个对象有什么"变得没人说得清——模具的意义就没了。
让 print 说人话:__str__
直接打印对象,输出是一串"火星文":
__str__ 和 __init__ 一样是双下划线家族(魔法方法):你不直接喊它们,Python 在特定时机自动喊——造对象时喊 __init__,print 时喊 __str__。魔法方法还有一大家子,现阶段认识这两个就够;以后在别人的代码里看到 __xxx__,知道"这是给 Python 自动调用的钩子"即可。
完整体检:一个类的解剖图
这就是你现阶段需要的全部零件:docstring、__init__ 声明齐属性(含容器和默认值)、若干动词方法(带检查、带默认参数)、__str__ 自我介绍。以后你读到的绝大多数类,骨架都长这样——零件多点少点而已。
✍️ 练习
练习 1:找茬修类
下面的类有两处 self 相关的错误(正是本课警告过的两个高频坑),修好让它跑通。
💡 提示
错误一在 add 的参数表(少了谁?);错误二在 report 的 f-string 里(task 是属性还是局部变量?)。
✅ 参考答案
class Timer:
def __init__(self, task):
self.task = task
self.minutes = 0
def add(self, minutes): # 方法第一个参数必须是 self
self.minutes = self.minutes + minutes
def report(self):
return f"{self.task} 已计时 {self.minutes} 分钟" # 属性要带 self.
t = Timer("写周报")
t.add(25)
print(t.report())
练习 2:客服工单类
客服主管丽丽要管工单。写一个 Ticket 类:属性 customer(客户名)、issue(问题描述)、status(默认 "待处理");方法 close() 把 status 改成 "已解决";加 __str__ 输出形如 [待处理] 老王:无法登录。造两张工单,关掉其中一张,分别打印。
✅ 参考答案
class Ticket:
def __init__(self, customer, issue, status="待处理"):
self.customer = customer
self.issue = issue
self.status = status
def close(self):
self.status = "已解决"
def __str__(self):
return f"[{self.status}] {self.customer}:{self.issue}"
t1 = Ticket("老王", "无法登录")
t2 = Ticket("小陈", "重复扣款")
t1.close()
print(t1)
print(t2)
练习 3:对象进容器(挑战)
对象和列表/推导式可以随意组合。用上面的 Ticket 类(答案里的版本已就位):① 建一个含 4 张工单的列表 ② 关掉其中两张 ③ 用列表推导式筛出所有"待处理"工单并逐个打印。
💡 提示
对象的属性照常能进 if:[t for t in tickets if t.status == "待处理"]。打印用 for 逐个 print(print 会自动走 __str__)。
✅ 参考答案
class Ticket:
def __init__(self, customer, issue, status="待处理"):
self.customer = customer
self.issue = issue
self.status = status
def close(self):
self.status = "已解决"
def __str__(self):
return f"[{self.status}] {self.customer}:{self.issue}"
tickets = [
Ticket("老王", "无法登录"),
Ticket("小陈", "重复扣款"),
Ticket("丽丽", "页面报错"),
Ticket("大壮", "忘记密码"),
]
tickets[0].close()
tickets[2].close()
pending = [t for t in tickets if t.status == "待处理"]
for t in pending:
print(t)
"一列表的对象 + 推导式筛选"是真实项目的日常形态——上单元的容器功夫和本单元的类,就此合流。
📝 随堂测验
1. __init__ 方法在什么时候执行?
2. ada.report() 调用时,方法定义里的 self 是谁?
3. 方法里写 self.count = self.count + 1 和直接写 count = count + 1 的区别是?
4. 定义方法时写 def add(minutes): 忘了 self,调用 t.add(25) 会怎样?
5. 给类写 __str__ 方法的作用是?
本课小结
class 类名:(首字母大写)定义模具;__init__(self, ...)是出厂设置,造对象时自动执行- self = "我":说明书里的代词;
对象.方法()时 Python 自动把对象传给 self - 属性挂在 self 上(
self.x = ...),方法能读能改;属性在__init__里声明齐是规矩 - 两大高频坑:方法忘写 self 参数(TypeError)、属性忘写
self.前缀(NameError) __str__让 print 说人话;双下划线方法 = Python 自动调用的钩子
下一课看继承:新模具怎么"站在旧模具的肩膀上"——以及为什么这个特性要克制着用。
划选正文任意文字可高亮、批注或加入复习卡
讨论
载入中…