CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
Python 语言基础/课时/类与对象入门
RawBlame
课时目录12 节

Python 语言基础

  1. 01Python 是什么
  2. 02语法与缩进
  3. 03类型与变量
  4. 04条件与循环
  5. 05函数
  6. 06列表字典与集合
  7. 07字符串处理
  8. 08文件读写
  9. 09模块与 pip
  10. 10类与对象入门
  11. 11异常处理
  12. 12综合练习与下一步

类与对象入门

class、__init__、方法。

40 分钟

类与对象入门

这一课你将能做到

  1. 用 class 定义带 __init__、实例方法、__str__ 的类,并创建多个互不干扰的对象
  2. 说清 self 是什么、实例属性与类属性的区别
  3. 写出基本的类继承,用 super().__init__() 复用父类初始化逻辑

为什么要学

前九课你写的是「函数 + 数据」——数据散在变量里,函数挨个收参数。当数据成组出现(每个用户都有名字、年龄、邮箱),传参就会越传越长。类把「数据 + 操作这组数据的方法」打包成一个类型:user = User("小明", 18),之后 user.greet() 自带上下文。标准库和第三方库几乎全是类:Path 对象、requests.Session、大模型客户端——不认识类,读文档都费劲。

人话:类是图纸,对象是照图纸造出来的房子。图纸定义「有哪些房间(属性)、能做什么事(方法)」,每栋房子数据各不相同、能力一样。造房子这个动作叫实例化。

先建立图像

class User:                ← 图纸(类)
    ├─ name  = ?           ← 属性槽位:每栋房子填自己的值
    └─ greet()             ← 方法:所有房子共用的能力

u1 = User("小明")           ← 造第一栋房子(实例化)
u2 = User("小红")           ← 造第二栋房子

u1.name → "小明"            ← 各自的数据
u2.name → "小红"
u1.greet is u2.greet → True ← 方法是共用的同一份

与 Java 对照:

// Java:构造器与方法分语法位,属性需声明类型
public class User {
    private String name;
    public User(String name) { this.name = name; }
    public void greet() { System.out.println("hi " + name); }
}
# Python:__init__ 即构造器,self 显式写在参数首位
class User:
    def __init__(self, name):
        self.name = name
    def greet(self):
        print(f"hi {self.name}")

跟着做

步骤 1:定义第一个类并实例化

新建 oop_demo.py:

class User:
    def __init__(self, name, age):      # 构造方法:创建对象时自动调用
        self.name = name                # 实例属性:贴在对象身上的数据
        self.age = age

    def greet(self):                    # 实例方法:第一个参数固定 self
        return f"我是 {self.name},{self.age} 岁"

    def birthday(self):                 # 方法可以修改自己的属性
        self.age += 1

u1 = User("小明", 18)                   # 实例化:类名(参数) → 自动传给 __init__
u2 = User("小红", 20)

print(u1.greet())                       # 我是 小明,18 岁
print(u2.greet())                       # 我是 小红,20 岁

u1.birthday()                           # 只影响 u1
print(u1.age, u2.age)                   # 19 20

输出:

我是 小明,18 岁
我是 小红,20 岁
19 20

关键体感:u1.greet() 实际是 User.greet(u1) 的语法糖——self 接到的就是 u1。方法不需要手动传 self,Python 替你传。

步骤 2:self 到底是什么

class Cat:
    def __init__(self, name):
        self.name = name

    def who(self):
        print(id(self))                 # 对象内存身份

mimi = Cat("咪咪")
print(id(mimi))                          # 和下面打印的 id 相同
mimi.who()

# 等价的手动调用形式:
Cat.who(mimi)                            # 显式把对象当参数传入

输出(id 数值每次运行不同,两行一致即可):

2198712345671
2198712345671
2198712345671

结论:self 就是「当前对象本身」。调用 mimi.who() 时 Python 自动把 mimi 塞给 self。名字叫 self 只是惯例,技术上叫别的也能跑——但没有任何理由不叫 self。

步骤 3:实例属性 vs 类属性

class Dog:
    species = "犬科"                     # 类属性:所有实例共享

    def __init__(self, name):
        self.name = name                 # 实例属性:每个对象独有

d1, d2 = Dog("旺财"), Dog("小黑")
print(d1.species, d2.species)            # 犬科 犬科(共享)
Dog.species = "Canidae"
print(d1.species, d2.species)            # Canidae Canidae(跟着类变)

d1.species = "特殊犬"                    # ⚠ 这是在 d1 身上新建实例属性,
print(d1.species, d2.species)            # 特殊犬 Canidae(d2 不受影响)

输出:

犬科 犬科
Canidae Canidae
特殊犬 Canidae

规则:读属性先找实例、再找类;给 实例.类属性 赋值 = 新建同名实例属性遮蔽类属性。所有实例共享的数据放类属性,独有数据放实例属性。

步骤 4:__str__ 让 print 出人话

class Point:
    def __init__(self, x, y):
        self.x, self.y = x, y

p = Point(3, 5)
print(p)          # 默认输出:<__main__.Point object at 0x000001...> 没人话

默认输出:

<__main__.Point object at 0x0000021A5F3B7D90>

加上 __str__:

class Point:
    def __init__(self, x, y):
        self.x, self.y = x, y

    def __str__(self):
        return f"Point({self.x}, {self.y})"

p = Point(3, 5)
print(p)          # Point(3, 5)

输出:

Point(3, 5)

__str__ 是「魔术方法」(双下划线命名)之一:print() 触发它。同类成员还有 __repr__(调试表示)、__eq__(== 比较)、__len__ 等——入门阶段先掌握 __str__ 和 __eq__。

步骤 5:继承与 super()

class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        return f"{self.name} 发出声音"

class Dog(Animal):                       # 括号里写父类
    def __init__(self, name, breed):
        super().__init__(name)           # 复用父类初始化
        self.breed = breed               # 追加自己的属性

    def speak(self):                     # 重写(override)父类方法
        return f"{self.name}:汪汪!"

class Cat(Animal):
    def speak(self):
        return f"{self.name}:喵~"

dog = Dog("旺财", "柴犬")
cat = Cat("咪咪")
print(dog.speak())                       # 旺财:汪汪!
print(cat.speak())                       # 咪咪:喵~
print(isinstance(dog, Animal))           # True:狗是动物

# 多态:同一个调用,不同子类各自表现
for a in (dog, cat):
    print(a.speak())

输出:

旺财:汪汪!
咪咪:喵~
True
旺财:汪汪!
咪咪:喵~

步骤 6:小实战——用类管理银行账户

class Account:
    def __init__(self, owner, balance=0):
        self.owner = owner
        self.balance = balance

    def deposit(self, amount):
        if amount <= 0:
            raise ValueError("存款必须为正数")    # 第 11 课细讲异常
        self.balance += amount
        return self.balance

    def withdraw(self, amount):
        if amount > self.balance:
            raise ValueError("余额不足")
        self.balance -= amount
        return self.balance

    def __str__(self):
        return f"{self.owner} 的账户,余额 {self.balance}"

acc = Account("小明", 100)
acc.deposit(50)
print(acc)                          # 小明 的账户,余额 150
acc.withdraw(30)
print(acc)                          # 小明 的账户,余额 120

输出:

小明 的账户,余额 150
小明 的账户,余额 120

这个例子展示类的本质价值:数据(balance)和规则(不能负存款、不能透支)绑在一个类型里,外部只能通过方法操作,逻辑不会被绕过。

讲清楚要点

  1. 类是模板,对象是实例:User(...) 调用触发 __init__,self 指向新创建的对象。实例属性在 __init__ 里用 self.x = ... 挂上,每个对象一份。
  2. self 必须是实例方法的第一个参数:定义时写上、调用时省略(Python 自动传)。遗漏 self 是初学者最高频的 TypeError 来源。
  3. __init__ 是初始化不是分配内存:内存分配由 __new__ 完成(入门不必深究),__init__ 只负责把属性填好。它不需要 return(返回非 None 反而报错)。
  4. 实例属性遮蔽类属性:读取时先实例后类;赋值 obj.attr = ... 永远作用于实例。共享常量放类属性,注意别误给「实例.类属性」赋值。
  5. 魔术方法让自定义类型融入语法:__str__ 管 print、__eq__ 管 ==、__repr__ 管调试输出。Python 不用实现接口,实现对应魔术方法即可参与内建语法。
  6. 继承 + super 复用:子类重写方法即多态;super().__init__() 保证父类逻辑先跑。组合优于继承的老话先记着:能用「类里装另一个类的对象」就别硬造继承链。
  7. Python 没有 private 关键字:下划线前缀 _name 只是约定「内部使用」,语言不强制。这是 Python「我们都是成年人」的哲学。

你可能会懵的地方

  • 方法里忘写 self 为什么报错? def greet(): return f"hi {self.name}" 调用 u1.greet() 时 Python 会传一个参数进去,而函数不收参数 → TypeError: greet() takes 0 positional arguments but 1 was given。报错里「但 1 was given」指的就是自动传入的 self。

  • __init__ 和 Java 构造器的区别? 形式上:Python 构造调用就是「类名(...)」,__init__ 不用 new 关键字;Java 构造器可以重载多个,Python 靠默认参数/类方法工厂模拟重载。语义上两者都是「新建对象后初始化属性」。

  • 什么时候用类,什么时候用函数就够了? 数据只有一两个字段、操作彼此独立 → 函数 + 参数足够;数据成组、方法和这组数据强相关、需要多份实例 → 类。别为了「面向对象」硬包一层,也别在传参超过四个时还硬扛着不建类。

  • 继承多层的 super() 到底找谁? 沿「方法解析顺序(MRO)」找,单继承链就是逐级向上;多继承按 C3 线性化算法决定顺序(ClassName.__mro__ 可查看)。入门阶段记住「super 调用的是 MRO 上的下一个」即可。

  • @property 是什么? 把方法伪装成属性:@property def age(self): return self._age 之后 obj.age 像读属性一样调用方法,可用于加校验、保持兼容。第 10 课之后进阶再学,见到代码里 @ 开头的行是「装饰器」语法。

错法对照

错法问题正解
def greet(): 忘写 self 参数TypeError: 1 was givendef greet(self):
__init__ 里 name = name创建了局部变量,实例属性没挂上self.name = name
在 __init__ 里 return selfTypeError: 返回必须为 None__init__ 不写 return
d1.species = "x" 想改类属性只是新建实例属性遮蔽Dog.species = "x" 改类
子类重写 __init__ 忘调 super父类属性没初始化super().__init__(...)
所有东西都塞进类过度设计无状态工具用函数
print(u1) 直接看属性打出对象地址实现 __str__

卡点急救

  1. TypeError: takes 0 positional arguments but 1 was given:实例方法漏了 self,补上第一个参数。
  2. AttributeError: 'X' object has no attribute 'y':属性没在 __init__ 里初始化,或拼错名字。检查 self.y 赋值语句。
  3. 所有对象共享了同一份数据:把可变默认值或实例数据写成了类属性(class C: items = [])。移进 __init__:self.items = []。
  4. 子类实例缺父类的属性:重写 __init__ 忘了 super().__init__(),补上。
  5. print 对象显示一串地址:没实现 __str__,按步骤 4 加上。
  6. __init__ 报 TypeError: init() should return None:里面写了 return 值,删掉。

面试常问

  • 解释 self,为什么方法第一个参数必须是它? self 指向当前实例。obj.method(x) 等价于 Class.method(obj, x)——Python 把方法当「绑定了实例的普通函数」处理,实例作为第一个实参显式传入。这是 Python 显式优于隐式哲学的体现(对比 Java/C++ 的隐式 this)。

  • __init__ 与 __new__ 的区别? __new__ 负责创建并返回实例(静态方法,真正的「分配」),__init__ 负责初始化刚创建的实例(不返回值)。调用顺序:先 __new__ 后 __init__。自定义不可变类型或实现单例时才需要动 __new__。

  • 实例属性、类属性的区别与查找顺序? 实例属性存放在实例 __dict__,每个对象独立;类属性存放在类 __dict__,所有实例共享。读取按「实例 → 类 → 父类(MRO)」查找;通过实例赋值只会创建同名实例属性,不会修改类属性。

  • Python 如何实现多态?和 Java 的区别? Python 是鸭子类型:不要求共同父类或接口,只要对象有同名方法就能多态调用(「走起来像鸭子就叫它鸭子」)。Java 需要显式继承/接口约束。Python 的继承与魔术方法是锦上添花,不是多态的门槛。

  • __str__ 和 __repr__ 的区别? __str__ 面向用户,print()/str() 调用,追求可读;__repr__ 面向开发者,交互式回显/repr() 调用,追求无歧义(理想是能重建对象)。只实现一个时通常先实现 __repr__(str 缺失时会退回 repr)。

自测三问

  1. u1.greet() 和 User.greet(u1) 是什么关系?这解释了 self 的什么机制?
  2. 类属性和实例属性在「读」和「写」两个方向上行为有什么不同?
  3. 写出 Dog 继承 Animal 的 __init__ 骨架,并说明 super() 那一行的作用。

小结一句话

类 = 数据 + 方法的图纸,__init__ 挂属性、self 指对象、__str__ 管打印、继承配 super 实现复用与多态——认识类的第一天就要能读懂标准库里的对象。

延伸

  • 官方教程 — 类
  • 数据模型(魔术方法总览)
  • 下一课预告:异常处理——try/except/finally、raise 与自定义异常,让程序崩得体面、错得可控。
加载本节练习…
← 上一课模块与 pip下一课 →异常处理