Python 中的类完全指南
本文档面向零基础新手,目标是让你不只”会用类”,而是真正理解:
- 类是什么、实例是什么、self 是什么
- init 何时执行、有什么作用
- 属性和方法的区别
- 给属性指定默认值的多种方式
- 修改属性的两种方式(直接改 vs 通过方法改)
- 类变量 vs 实例变量的差别
- 继承:子类怎么继承父类、怎么覆盖父类方法
- 多层继承的查找顺序(MRO)
- super() 是什么、什么时候用
配有大量示例,全部可以直接运行。
第一部分:为什么要用类?
1.1 没有类之前,数据很难”打包管理”
假设你要管理多只狗的信息,不用类的写法:
dog1_name = "旺财"
dog1_age = 3
dog2_name = "小黑"
dog2_age = 2
def bark(name):
print(name + ":汪汪!")
bark(dog1_name) # 旺财:汪汪!
bark(dog2_name) # 小黑:汪汪!
当狗变多时,变量名越来越难管,属于”哪只狗”的数据越来越难追踪。
1.2 用类之后,数据和行为打包在一起
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
def bark(self):
print(self.name + ":汪汪!")
dog1 = Dog("旺财", 3)
dog2 = Dog("小黑", 2)
dog1.bark() # 旺财:汪汪!
dog2.bark() # 小黑:汪汪!
每只狗的数据(name、age)和行为(bark)都在”Dog”这个蓝图里,管起来清晰、好扩展。
第二部分:类与实例的概念
2.1 类(Class)——蓝图/模板
类 是一份”蓝图”:
- 描述这一类事物有什么数据(属性)
- 描述这一类事物能做什么(方法)
类本身不是一个具体对象,它只是定义”一类事物的规格”。
2.2 实例(Instance)——根据蓝图造出来的具体对象
实例 是根据类创建出来的具体对象:
- 可以有自己的属性值
- 可以调用类里定义的方法
一个类可以创建无数个实例,每个实例都独立存储自己的属性值。
2.3 生活中的类比
| 概念 | 生活对应 |
|---|---|
| 类 | 汽车设计图(规定有哪些配置) |
| 实例 | 根据图纸造出来的每一辆具体的车 |
class Car:
def __init__(self, brand, color):
self.brand = brand
self.color = color
def info(self):
return f"{self.color}色的{self.brand}"
# 两辆不同的车(两个实例)
car1 = Car("宝马", "白")
car2 = Car("奔驰", "黑")
print(car1.info()) # 白色的宝马
print(car2.info()) # 黑色的奔驰
第三部分:定义类的基本语法
3.1 语法结构
class 类名:
"""类的文档字符串(可选)"""
def __init__(self, 形参1, 形参2): # 构造方法
self.属性1 = 形参1
self.属性2 = 形参2
def 方法名(self): # 普通方法
# 方法体
pass
规则:
class关键字开头,类名用大驼峰(首字母大写,例如Dog、BankAccount)- 类体要缩进(4 个空格)
- 类里面定义的函数叫方法,第一个参数固定写 self
第四部分:init 方法——构造方法(创建实例时自动执行)
4.1 init 是什么?
__init__ 是一个特殊方法(两边各有两个下划线),叫做构造方法。
当你写 Dog("旺财", 3) 创建实例时,Python 会:
- 先创建出一个”空白”的 Dog 实例
- 自动把这个实例传给 self
- 自动调用
__init__,把你传的"旺财"和3传给name和age
你不需要自己调用 __init__,Python 会自动调用。
class Dog:
def __init__(self, name, age):
print(f"__init__ 被调用了,name={name}, age={age}")
self.name = name
self.age = age
dog = Dog("旺财", 3) # 输出:__init__ 被调用了,name=旺财, age=3
4.2 self 是什么?
self 表示当前这个实例本身。
当你写 dog = Dog("旺财", 3) 时:
- Python 把新创建的 dog 对象传给
self - 所以
self.name = name就是”在这个 dog 对象上,把属性 name 设为 ‘旺财'”
每次通过实例调用方法时,Python 会把”当前实例”自动传给第一个参数(self),所以你调用时不需要手动传 self。
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
def bark(self):
# self 就是调用这个方法的那只狗
print(self.name + ":汪汪!")
dog1 = Dog("旺财", 3)
dog2 = Dog("小黑", 2)
dog1.bark() # Python 自动把 dog1 传给 self → 旺财:汪汪!
dog2.bark() # Python 自动把 dog2 传给 self → 小黑:汪汪!
第五部分:访问属性和调用方法
5.1 访问属性:实例.属性名
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
dog = Dog("旺财", 3)
print(dog.name) # 旺财
print(dog.age) # 3
5.2 调用方法:实例.方法名()
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
def bark(self):
print(self.name + ":汪汪!")
def info(self):
return f"这只狗叫 {self.name},今年 {self.age} 岁"
dog = Dog("旺财", 3)
dog.bark() # 旺财:汪汪!
print(dog.info()) # 这只狗叫 旺财,今年 3 岁
第六部分:使用类和实例——更多示例
6.1 每个实例的属性相互独立
同一个类创建的不同实例,各自拥有一份自己的属性,互不干扰。
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
dog1 = Dog("旺财", 3)
dog2 = Dog("小黑", 2)
dog1.age = 5 # 只改了 dog1 的 age
print(dog1.age) # 5
print(dog2.age) # 2(dog2 没有变)
6.2 在方法里访问其他属性和调用其他方法
方法里可以通过 self.属性名 访问当前实例的任意属性,也可以用 self.方法名() 调用当前实例的其他方法。
class Dog:
def __init__(self, name, age, breed):
self.name = name
self.age = age
self.breed = breed
def bark(self):
print(self.name + ":汪汪!")
def full_info(self):
# 在方法里调用另一个方法
self.bark()
return f"品种:{self.breed},年龄:{self.age} 岁"
dog = Dog("旺财", 3, "金毛")
print(dog.full_info())
# 旺财:汪汪!
# 品种:金毛,年龄:3 岁
第七部分:给属性指定默认值
7.1 方式一:给 init 的形参设默认值
和函数的默认参数一样,形参可以有默认值。调用时不传就用默认值,传了就用传入的值。
注意:有默认值的参数要放在无默认值参数的后面。
class Dog:
def __init__(self, name, age, breed="未知"):
self.name = name
self.age = age
self.breed = breed
dog1 = Dog("旺财", 3) # breed 用默认值 "未知"
dog2 = Dog("小黑", 2, "哈士奇") # breed = "哈士奇"
print(dog1.breed) # 未知
print(dog2.breed) # 哈士奇
7.2 方式二:在 init 里直接赋固定值(不从参数传入)
有些属性每个实例创建时都应该有一个固定的初始值,不需要用户传入,就直接在 __init__ 里写死。
class BankAccount:
def __init__(self, owner):
self.owner = owner
self.balance = 0 # 每个账户初始余额都是 0,不需要用户传入
acc = BankAccount("小明")
print(acc.balance) # 0
7.3 可选属性:用 None 做默认值
当某个属性”有时有、有时没有”时,常用 None 做默认值,在需要时再赋值。
class Person:
def __init__(self, name, email=None):
self.name = name
self.email = email # 默认没有邮箱
def contact(self):
if self.email is None:
return f"{self.name} 没有留邮箱"
return f"{self.name} 的邮箱:{self.email}"
p1 = Person("小明")
p2 = Person("小红", "xh@email.com")
print(p1.contact()) # 小明 没有留邮箱
print(p2.contact()) # 小红 的邮箱:xh@email.com
第八部分:修改属性的值
8.1 方式一:直接用 实例.属性名 = 新值
最简单直接的方式,适合”简单赋值、不需要额外逻辑”的场景。
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
dog = Dog("旺财", 3)
print(dog.age) # 3
dog.age = 5 # 直接修改
print(dog.age) # 5
8.2 方式二:通过方法修改属性(更推荐)
当修改时需要做验证、计算,或者希望改逻辑集中在一个地方,就在类里写一个方法来修改属性。
class BankAccount:
def __init__(self, owner):
self.owner = owner
self.balance = 0
def deposit(self, amount):
"""存款"""
if amount <= 0:
print("存款金额必须大于 0")
return
self.balance += amount
print(f"存入 {amount} 元,当前余额:{self.balance} 元")
def withdraw(self, amount):
"""取款"""
if amount <= 0:
print("取款金额必须大于 0")
return
if amount > self.balance:
print("余额不足")
return
self.balance -= amount
print(f"取出 {amount} 元,当前余额:{self.balance} 元")
acc = BankAccount("小明")
acc.deposit(500) # 存入 500 元,当前余额:500 元
acc.withdraw(200) # 取出 200 元,当前余额:300 元
acc.withdraw(500) # 余额不足
8.3 什么时候用哪种方式?
| 场景 | 建议 |
|---|---|
| 只是简单赋值,不需要验证/计算 | 直接 实例.属性 = 新值 |
| 修改时需要验证(如不能为负) | 写一个专门的方法 |
| 希望将来统一改逻辑 | 写方法,所有调用处不用动 |
第九部分:类变量 vs 实例变量
9.1 实例变量:属于每个实例自己的
到目前为止我们用的 self.name、self.age 都是实例变量。每个实例各自有一份,互不影响。
9.2 类变量:属于整个类(所有实例共享)
在 __init__ 外面直接定义的变量叫类变量,所有实例共享。
class Dog:
# 类变量:记录总共创建了多少只狗
count = 0
def __init__(self, name):
self.name = name
Dog.count += 1 # 每创建一只狗,类变量 count 加 1
dog1 = Dog("旺财")
dog2 = Dog("小黑")
dog3 = Dog("大白")
print(Dog.count) # 3(类变量通过类名访问)
print(dog1.count) # 3(实例也可以访问类变量,结果相同)
9.3 类变量与实例变量同名时的坑
如果实例给”同名属性”赋值,会遮蔽(shadow)类变量,而不是修改类变量:
class Dog:
count = 0
def __init__(self, name):
self.name = name
Dog.count += 1
dog1 = Dog("旺财")
dog1.count = 999 # 这里在 dog1 上创建了一个实例属性 count,不影响类变量
print(dog1.count) # 999(实例变量)
print(Dog.count) # 1(类变量没有变)
建议:修改类变量时统一写 类名.类变量名 = 新值,不要通过实例修改。
第十部分:继承
10.1 什么是继承?
继承是让子类“拥有父类的所有属性和方法”,然后在这个基础上可以:
- 新增子类自己特有的属性或方法
- 覆盖(重写)父类的方法,实现不同的行为
class Animal:
"""父类(基类)"""
def __init__(self, name):
self.name = name
def speak(self):
print(self.name + " 发出声音")
class Dog(Animal):
"""子类,继承 Animal"""
def speak(self):
print(self.name + ":汪汪!") # 覆盖父类的 speak
class Cat(Animal):
"""子类,继承 Animal"""
def speak(self):
print(self.name + ":喵~") # 覆盖父类的 speak
dog = Dog("旺财")
cat = Cat("咪咪")
dog.speak() # 旺财:汪汪!
cat.speak() # 咪咪:喵~
10.2 子类的 init 与 super()
如果子类没有定义 __init__,Python 会自动使用父类的 __init__。
如果子类有自己的 __init__,通常需要先调用 super().__init__(...) 让父类也能完成初始化:
class Animal:
def __init__(self, name, age):
self.name = name
self.age = age
def info(self):
return f"{self.name},{self.age} 岁"
class Dog(Animal):
def __init__(self, name, age, breed):
super().__init__(name, age) # 先让父类初始化 name 和 age
self.breed = breed # 子类自己的属性
def info(self):
# 覆盖父类的 info
return f"{self.name},{self.age} 岁,品种:{self.breed}"
dog = Dog("旺财", 3, "金毛")
print(dog.info()) # 旺财,3 岁,品种:金毛
10.3 子类继承父类方法(不重写就直接用)
class Animal:
def __init__(self, name):
self.name = name
def breathe(self):
print(self.name + " 在呼吸")
class Dog(Animal):
def bark(self):
print(self.name + ":汪汪!")
dog = Dog("旺财")
dog.breathe() # 旺财 在呼吸(继承自父类,直接用)
dog.bark() # 旺财:汪汪!
第十一部分:方法解析顺序(MRO)
11.1 什么是 MRO?
当你调用一个方法时,Python 会按照一定顺序去查找这个方法:
- 先找当前类
- 再找父类(按继承链顺序)
这个顺序叫 MRO(Method Resolution Order,方法解析顺序)。
class A:
def hello(self):
print("A 的 hello")
class B(A):
pass # 没有重写 hello
class C(B):
pass # 没有重写 hello
c = C()
c.hello() # A 的 hello
# 查找顺序:C → B → A,C 和 B 里没有 hello,最终在 A 里找到
11.2 查看类的 MRO
print(C.__mro__)
# (<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
第十二部分:super() 详解
12.1 super() 是什么?
super() 返回”当前类的父类(按 MRO 顺序)”,让你调用父类的方法。
最常见用法:在子类 __init__ 里调用父类的 __init__。
12.2 不用 super() 会怎样?
class Animal:
def __init__(self, name):
self.name = name
class Dog(Animal):
def __init__(self, name, breed):
# 如果忘记调用 super().__init__(name)
self.breed = breed
dog = Dog("旺财", "金毛")
print(dog.breed) # 金毛
# print(dog.name) # AttributeError:name 没有被初始化!
12.3 正确使用 super()
class Animal:
def __init__(self, name):
self.name = name
print(f"Animal.__init__ 被调用,name={name}")
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) # 先让父类完成自己的初始化
self.breed = breed
print(f"Dog.__init__ 被调用,breed={breed}")
dog = Dog("旺财", "金毛")
# Animal.__init__ 被调用,name=旺财
# Dog.__init__ 被调用,breed=金毛
print(dog.name) # 旺财(父类初始化的属性)
print(dog.breed) # 金毛(子类自己的属性)
第十三部分:常见特殊方法(”魔法方法”)
13.1 str:让 print(实例) 显示有意义的内容
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return f"Dog({self.name}, {self.age}岁)"
dog = Dog("旺财", 3)
print(dog) # Dog(旺财, 3岁)
print(str(dog)) # Dog(旺财, 3岁)
13.2 len:让 len(实例) 有意义
class Bag:
def __init__(self):
self.items = []
def add(self, item):
self.items.append(item)
def __len__(self):
return len(self.items)
bag = Bag()
bag.add("苹果")
bag.add("香蕉")
print(len(bag)) # 2
第十四部分:完整综合示例
下面是一个”银行账户”类,综合运用了所有知识点:
class BankAccount:
"""银行账户类"""
bank_name = "Python银行" # 类变量:所有账户共享
def __init__(self, owner, balance=0):
self.owner = owner # 实例变量
self.balance = balance # 实例变量,默认余额为 0
def deposit(self, amount):
"""存款"""
if amount <= 0:
print("金额必须大于 0")
return
self.balance += amount
print(f"[{self.owner}] 存入 {amount} 元,余额:{self.balance} 元")
def withdraw(self, amount):
"""取款"""
if amount <= 0:
print("金额必须大于 0")
return
if amount > self.balance:
print(f"[{self.owner}] 余额不足(当前余额:{self.balance} 元)")
return
self.balance -= amount
print(f"[{self.owner}] 取出 {amount} 元,余额:{self.balance} 元")
def __str__(self):
return f"{BankAccount.bank_name} | 账户:{self.owner} | 余额:{self.balance} 元"
class SavingsAccount(BankAccount):
"""储蓄账户(继承 BankAccount),有利率"""
def __init__(self, owner, rate=0.02):
super().__init__(owner, balance=0) # 调用父类初始化
self.rate = rate
def apply_interest(self):
"""计算并加上利息"""
interest = self.balance * self.rate
self.balance += interest
print(f"[{self.owner}] 计息 {interest:.2f} 元,当前余额:{self.balance:.2f} 元")
def __str__(self):
return super().__str__() + f" | 利率:{self.rate * 100}%"
# 普通账户
acc = BankAccount("小明", 1000)
print(acc) # Python银行 | 账户:小明 | 余额:1000 元
acc.deposit(500) # [小明] 存入 500 元,余额:1500 元
acc.withdraw(2000) # [小明] 余额不足
acc.withdraw(300) # [小明] 取出 300 元,余额:1200 元
print()
# 储蓄账户
sav = SavingsAccount("小红", rate=0.05)
sav.deposit(1000)
sav.apply_interest() # [小红] 计息 50.00 元,当前余额:1050.00 元
print(sav) # Python银行 | 账户:小红 | 余额:1050.0 元 | 利率:5.0%
第十五部分:新手最常见的注意事项与坑
15.1 定义方法时忘记写 self
class Dog:
def bark(): # 错误!少了 self
print("汪汪!")
dog = Dog()
dog.bark() # TypeError: bark() takes 0 positional arguments but 1 was given
正确:
class Dog:
def bark(self): # 正确
print("汪汪!")
15.2 调用方法时传入了 self
class Dog:
def bark(self):
print("汪汪!")
dog = Dog()
# dog.bark(dog) # 错误!Python 会自动传 self
dog.bark() # 正确
15.3 用 = 比较 None 时,推荐用 is
if self.email is None: # 推荐
...
if self.email == None: # 可以用但不推荐
...
15.4 子类 init 忘记调用 super().init()
父类的属性不会被初始化,访问时会报 AttributeError。
15.5 类名用大驼峰,变量/方法名用下划线分割
class BankAccount: # 正确
def deposit_money(): # 方法名用下划线
pass
class bankaccount: # 不推荐
def DepositMoney(): # 不推荐
pass
第十六部分:小结表
| 概念 | 说明 |
|---|---|
| 类(class) | 用 class 类名: 定义,是”模板/蓝图” |
| 实例(instance) | 用 类名(参数) 创建,是具体对象 |
| init | 构造方法,创建实例时 Python 自动调用 |
| self | 表示当前实例,方法的第一个参数,调用时不需要手动传 |
| 实例变量 | self.属性名,属于每个实例自己 |
| 类变量 | 定义在方法外,所有实例共享,用 类名.变量名 修改 |
| 直接改属性 | 实例.属性 = 新值,简单场景适用 |
| 通过方法改属性 | 修改时需要验证/计算时更推荐 |
| 继承 | class 子类(父类):,子类拥有父类的属性和方法 |
| super() | 调用父类的方法,常用于子类 __init__ 里调用父类 __init__ |
| MRO | 方法查找顺序:当前类 → 父类 → 祖先类 |
| str | 让 print(实例) 显示有意义的内容 |