16.Python中的类完全指南

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 关键字开头,类名用大驼峰(首字母大写,例如 DogBankAccount
  • 类体要缩进(4 个空格)
  • 类里面定义的函数叫方法,第一个参数固定写 self

第四部分:init 方法——构造方法(创建实例时自动执行)

4.1 init 是什么?

__init__ 是一个特殊方法(两边各有两个下划线),叫做构造方法

当你写 Dog("旺财", 3) 创建实例时,Python 会:

  1. 先创建出一个”空白”的 Dog 实例
  2. 自动把这个实例传给 self
  3. 自动调用 __init__,把你传的 "旺财"3 传给 nameage

不需要自己调用 __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.nameself.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 会按照一定顺序去查找这个方法:

  1. 先找当前类
  2. 再找父类(按继承链顺序)

这个顺序叫 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(实例) 显示有意义的内容

发表评论