Python 类和实例:从图纸到房子的编程艺术
引言:建筑设计图与真实的房子
建筑师画了一张图纸:三室一厅、南北通透、带阳台。这张图纸本身不能住人,但根据它可以盖出一栋、十栋、一百栋真实的房子——每栋房子结构相同,但住着不同的人、摆着不同的家具。
Python 的面向对象编程(OOP)核心就是这两个概念:
- 类(Class):抽象的模板、图纸——比如"学生"这个概念;
- 实例(Instance):根据类创建的具体对象——比如"学生小明""学生小红"。
每个实例拥有相同的方法(都会考试、都会自我介绍),但各自的数据不同(小明 90 分,小红 85 分)。
一、定义第一个类
1.1 最简单的类定义
class Student(object):
pass逐部分拆解:
| 部分 | 含义 | 说明 |
|---|---|---|
class | 关键字 | 告诉 Python"我要定义一个类" |
Student | 类名 | 通常大写开头(驼峰命名法),如 Student、MyClass |
(object) | 继承的父类 | 没有合适的父类就写 object——所有类最终都继承它 |
pass | 占位符 | 暂时什么都不做,先占个位置 |
关于 (object):这是"继承"的语法,表示 Student 类是从 object 类派生出来的。继承的详细概念后面会讲,现在只需记住:不知道继承谁就写 object。
注意:Python 3 中
(object)可以省略不写,class Student:也完全合法。但写上更明确,兼容旧代码习惯。
1.2 创建实例:图纸变成房子
>>> bart = Student()
>>> bart
<__main__.Student object at 0x10a67a590>Student() 就像"按图纸开工",bart 就是盖好的那栋房子——一个具体的 Student 实例。后面的 0x10a67a590 是它在内存中的地址,每个实例地址都不同。
而 Student 本身呢?它仍然是类(图纸):
>>> Student
<class '__main__.Student'>生活化理解:Student 是图纸,Student() 是开工命令,bart 是盖好的房子。
1.3 给实例绑定属性
Python 非常灵活,可以随时给实例"贴标签":
>>> bart.name = 'Bart Simpson'
>>> bart.name
'Bart Simpson'这就像给房子挂门牌——房子盖好后,随时可以挂上"Bart 家"的牌子。
二、__init__ 方法:强制填写"必填项"
2.1 为什么要用 __init__?
上面的方式有个问题:创建实例后再逐个绑定属性,容易遗漏。如果每个学生必须有姓名和成绩,能不能在"开工"时就强制填写?
可以!定义 __init__ 方法:
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score⚠️ 注意:
__init__前后各有两个下划线!不是_init_,也不是__init。
2.2 __init__ 的特殊之处
第一个参数永远是 self
self 指向正在被创建的实例本身。在 __init__ 内部,self.name = name 的意思是:"把传入的 name 绑定到当前这个实例上。"
创建实例时必须传入匹配的参数
有了 __init__,就不能空着手创建了:
>>> bart = Student('Bart Simpson', 59)
>>> bart.name
'Bart Simpson'
>>> bart.score
59但注意:self 不需要手动传!Python 解释器会自动把实例变量传进去。你只需要传 name 和 score。
生活化理解:__init__ 像购房合同上的"必填信息"——姓名、身份证号必须当场填好,房子才能交付。self 就是"这套房子本身",销售人员(Python)会自动把"这套房子"填进合同,你只需要填个人信息。
2.3 self 到底是什么?
self 不是 Python 关键字,它只是一个约定俗成的名字(你完全可以写成 this、me,但全世界 Python 程序员都用 self)。
它的作用:让实例的方法知道自己在操作哪个实例的数据。
bart = Student('Bart', 59)
lisa = Student('Lisa', 87)
bart.print_score() # self 就是 bart,打印 Bart 的分数
lisa.print_score() # self 就是 lisa,打印 Lisa 的分数同一个方法,因为 self 指向不同实例,操作的数据就不同。
三、类的方法:把数据和操作绑在一起
3.1 从"外部函数"到"内部方法"
假设我们要打印学生成绩。最直观的做法是写个外部函数:
def print_score(std):
print('%s: %s' % (std.name, std.score))
>>> print_score(bart)
Bart Simpson: 59这能工作,但有个问题:数据和操作分离了。Student 实例自己拥有 name 和 score,却要依赖外面的函数来打印——就像你家里有米有锅,却非要叫外卖才能吃上饭。
3.2 数据封装:把操作收进类里
更好的做法:把打印逻辑写进 Student 类内部,作为方法:
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def print_score(self):
print('%s: %s' % (self.name, self.score))调用时直接在实例上点出来:
>>> bart.print_score()
Bart Simpson: 59这就是封装(Encapsulation):数据(name、score)和操作数据的逻辑(print_score)被"打包"进同一个类里。外部调用者只需要知道"给 name 和 score 就能创建学生,调用 print_score 就能打印成绩",不需要知道内部怎么实现的。
3.3 再封装一个方法:成绩等级
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def get_grade(self):
if self.score >= 90:
return 'A'
elif self.score >= 60:
return 'B'
else:
return 'C'使用:
lisa = Student('Lisa', 99)
bart = Student('Bart', 59)
print(lisa.name, lisa.get_grade()) # Lisa A
print(bart.name, bart.get_grade()) # Bart C外部调用者不需要知道"90 分以上是 A"这个规则,直接调用 get_grade() 就行。内部规则随时可以改(比如 95 分以上才是 A),只要方法名和返回格式不变,外部代码零改动。
四、Python 的"自由"与风险
4.1 实例属性可以动态增删
和 Java、C++ 等静态语言不同,Python 允许给实例临时绑定任何属性:
>>> bart = Student('Bart', 59)
>>> lisa = Student('Lisa', 87)
>>> bart.age = 8 # 给 bart 单独加一个 age 属性
>>> bart.age
8
>>> lisa.age # lisa 没有这个属性,报错!
AttributeError: 'Student' object has no attribute 'age'两个实例来自同一个类,但拥有的属性可以不同——这是 Python 的灵活性,也是潜在的风险。
4.2 风险:属性不一致导致代码脆弱
def print_age(std):
print(std.age) # 假设所有学生都有 age
print_age(bart) # 正常
print_age(lisa) # 崩溃!建议:
- 类应该有的属性,在
__init__中统一定义,不要依赖动态绑定; - 动态绑定只用于临时、个别的场景,别当成常规操作。
五、类 vs 实例:一张表看懂区别
| 对比项 | 类(Class) | 实例(Instance) |
|---|---|---|
| 本质 | 抽象的模板、图纸 | 具体的对象、房子 |
| 创建方式 | class Student(object): | bart = Student('Bart', 59) |
| 数量 | 一个类 | 可以创建无数个实例 |
| 数据 | 不存具体数据(存方法定义) | 各自独立的数据(name、score) |
| 方法 | 定义方法(第一个参数是 self) | 调用方法(self 自动传入) |
| 内存 | 一份类定义 | 每个实例独立占用内存 |
六、常见误区与避坑指南
6.1 误区一:__init__ 写成 _init_ 或 init
# ❌ 错误:前后只有一个下划线
def _init_(self, name):
self.name = name
# ❌ 错误:没有下划线
def init(self, name):
self.name = name
# ✅ 正确:前后各两个下划线
def __init__(self, name):
self.name = name写错不会报错,但创建实例时不会自动调用,属性就没绑定上——排查起来非常痛苦。
6.2 误区二:忘记写 self
class Student(object):
def __init__(self, name):
name = name # ❌ 错!这只是局部变量赋值
self.name = name # ✅ 对!绑定到实例上name = name 只是把参数赋给自己,方法结束就消失。必须 self.name = name 才能存到实例里。
6.3 误区三:调用方法时手动传 self
bart = Student('Bart', 59)
# ❌ 错误:多此一举
bart.print_score(bart)
# ✅ 正确:self 自动传入
bart.print_score()6.4 误区四:把实例属性当类属性
class Student(object):
count = 0 # 这是类属性,所有实例共享
def __init__(self, name):
self.name = name # 这是实例属性,每个实例独立
bart = Student('Bart')
lisa = Student('Lisa')
Student.count += 1
print(bart.count) # 1
print(lisa.count) # 1 —— 共享的!类属性像"班级公告栏"——所有人看到的是同一块;实例属性像"个人课桌"——各用各的。别搞混。
6.5 误区五:过度设计
# ❌ 杀鸡用牛刀
class Adder(object):
def __init__(self, a, b):
self.a = a
self.b = b
def add(self):
return self.a + self.b
result = Adder(1, 2).add()
# ✅ 简单计算用函数就够了
def add(a, b):
return a + b
result = add(1, 2)判断标准:如果数据和操作是长期绑定的(学生有名字、成绩,还要算等级、打印),用类;如果只是一次性计算,用函数。
七、实战练习
练习 1:长方形类
定义一个 Rectangle 类:
__init__接收长和宽- 方法
area()返回面积 - 方法
perimeter()返回周长
r = Rectangle(3, 4)
print(r.area()) # 12
print(r.perimeter()) # 14参考答案
class Rectangle(object):
def __init__(self, length, width):
self.length = length
self.width = width
def area(self):
return self.length * self.width
def perimeter(self):
return 2 * (self.length + self.width)练习 2:银行账户类
定义一个 BankAccount 类:
__init__接收户名和初始余额- 方法
deposit(amount)存钱 - 方法
withdraw(amount)取钱(余额不足时提示) - 方法
get_balance()返回当前余额
acc = BankAccount('小明', 1000)
acc.deposit(500)
acc.withdraw(200)
print(acc.get_balance()) # 1300
acc.withdraw(2000) # 余额不足!参考答案
class BankAccount(object):
def __init__(self, owner, balance):
self.owner = owner
self.balance = balance
def deposit(self, amount):
self.balance += amount
print(f'存入 {amount},当前余额 {self.balance}')
def withdraw(self, amount):
if amount > self.balance:
print('余额不足!')
else:
self.balance -= amount
print(f'取出 {amount},当前余额 {self.balance}')
def get_balance(self):
return self.balance练习 3:找出 bug
下面代码创建学生后,为什么 print(bart.name) 报 AttributeError?
class Student(object):
def __init__(self, name):
name = name
bart = Student('Bart')
print(bart.name)参考答案
__init__ 里写的是 name = name,这只是把参数赋给局部变量,没有绑定到实例上。方法结束后,这个值就消失了。
修复:
def __init__(self, name):
self.name = name # 必须加 self.八、小结
- 类是模板,实例是对象——图纸 vs 房子,概念 vs 具体;
class Student(object):定义类,类名大写开头,父类默认 object;__init__方法在创建实例时自动调用,用于强制绑定必要属性;self指向实例本身,方法的第一个参数必须是它,但调用时不用传;- 封装 = 数据 + 操作数据的方法,外部只管用,不管内部实现;
- 实例属性各自独立,类属性所有实例共享——别搞混;
- Python 允许动态绑定属性,但应在
__init__中统一定义,避免属性不一致。
类和实例是面向对象编程的地基。理解了"图纸"和"房子"的关系,后面的继承、多态、魔法方法,都只是在这块地基上盖更高的楼层。