今天你 AI 赋能了吗?打开浏览器,在地址栏输入 xuefulu.ai ,按下 Enter 即可访问学府路1号!
2026年10月14日 星期三 10:40:31
常用网址: | | 学府路1号 https://xuefulu.ai | 下载 Python https://xuefulu.ai/Python.html | 下载 TRAE https://xuefulu.ai/Trae.html

一、为什么需要「类」(把数据和操作打包)

前面学过列表、字典、函数。真想写个「游戏角色管理」的小程序,你多半会这样描述一个角色:

Python
hero = {'名字': '小满', '等级': 18, '战力': 88}

一个角色没问题。可当你有 30 个角色,还要给每个人「出场问候」「算段位」时,就会发现一件别扭的事:数据(名字、战力)装在字典里,处理数据的函数却散落在外面。

Python
def 出场问候(角色):
    print('登场!我是', 角色['名字'])

def 算段位(角色):
    return '王者' if 角色['战力'] >= 90 else '青铜'

这么写能跑,但有两个隐患:① 字典的键全靠字符串,写成 角色['名子'] 要等到运行时才报错;② 数据和操作它的函数没有绑在一起,谁都能随手改,代码一长就乱。

类(class)要解决的正是这件事:把「这一类东西有哪些数据」和「它们能做什么」打包成一个整体,然后像用模具一样,造出一个一个具体的对象。

说法英文一句话
类class模具/图纸,描述「这类东西长什么样、能做什么」
对象(实例)object / instance用模具造出来的一块,各自装着不同的数据
属性attribute对象身上存的数据,如 name、age
方法method写在类里的函数,专门处理这类对象

打个比方:类=月饼模具,对象=压出来的一块块月饼。模具只有一个,月饼可以压很多块;每块月饼的馅儿(属性值)还能各不相同。

什么时候该用类?当你要反复描述「一类有共同特征的东西」时——学生、图书、银行账户、游戏角色。如果只是临时存几个数,列表和字典就够了,不必硬套类。

二、定义类与创建对象(class、__init__、self)

定义类的关键字是 class,类名习惯用「大驼峰」写法(每个单词首字母大写,如 BankAccount)。

Python
class Hero:
    def __init__(self, name, level):
        self.name = name
        self.level = level

短短四行,拆开看是三件事:

  1. class Hero: —— 声明「我要定义一类叫 Hero 的东西」,后面缩进的都归它管。
  2. def __init__(self, name, level): —— 初始化方法(init 前后各两个下划线)。创建对象时会自动执行,用来把初始数据装进去。
  3. self.name = name —— 把传进来的值,存到这个对象自己身上,名字叫 name。

创建对象不用 new,直接写「类名(参数)」就行:

Python
class Hero:
    def __init__(self, name, level):
        self.name = name
        self.level = level

h = Hero('小满', 18)
print(h.name)
print(h.level)

输出:

输出
小满
18

注意 Hero('小满', 18) 只传了两个参数,但 __init__ 明明收了三个——因为第一个 self 由 Python 自动填上,不需要你手动传。

写法含义
class Hero:定义类
def __init__(self, ...)创建对象时自动调用的初始化方法
self.name = name把值存到这个对象的属性上
Hero('小满', 18)按类造一个对象(会自动调用 __init__)
h.name读取对象的属性
一个类里可以有很多方法,但 __init__ 只有一个,它负责「一造出来就有哪些属性」。如果某个属性人人都有,就写在 __init__ 里;用的时候再给的,可以不写。

课程思政|工匠精神与严谨求实 · 类的名字,也要起得规矩

定义一个类,有几处小规矩:类名用大驼峰,__init__ 前后各两个下划线,该存的属性在初始化里一次交代清楚,self 一处都不能漏。这些细节看着碎,却是别人读你代码时最先碰到的东西。把属性一次初始化到位,就不会有「用到了才发现它不存在」的意外——严谨,是从造第一个类就开始练的。

三、实例属性与方法(self 到底是什么)

对象的属性(数据)和方法(行为)是绑在一起的。方法就是写在类里的函数,第一个参数固定是 self。

Python
class Hero:
    def __init__(self, name, power):
        self.name = name
        self.power = power

    def greet(self):
        print('登场!我是', self.name)

    def rank(self):
        if self.power >= 90:
            return '王者'
        return '青铜'

self 就是「当前正在操作的这个对象」。在方法里想拿到自己的数据,一律写 self.属性名:

Python
class Hero:
    def __init__(self, name, power):
        self.name = name
        self.power = power

    def greet(self):
        print('登场!我是', self.name)

    def rank(self):
        if self.power >= 90:
            return '王者'
        return '青铜'

a = Hero('小满', 92)
b = Hero('阿禾', 76)
a.greet()
b.greet()
print(a.name, '的段位:', a.rank())
print(b.name, '的段位:', b.rank())

输出:

输出
登场!我是 小满
登场!我是 阿禾
小满 的段位: 王者
阿禾 的段位: 青铜

同一个类造出的两个对象,各存各的数据、互不干扰:a.name 是「小满」,b.name 是「阿禾」。方法里的 self 会分别指向调用它的那个对象。

顺序写法说明
①self.属性 = 值给对象存一个属性
②self.属性读取对象自己的属性
③self.方法()调用对象自己的另一个方法
④对象.属性 / 对象.方法()在类外面用「点」访问
在类外面访问属性和方法都用「点」:a.name、a.greet()。方法后面的括号别忘——写 a.greet 只是拿到函数本身,不会执行。

四、类属性 vs 实例属性(最容易踩的坑)

属性其实分两种,位置不一样,含义也不一样:

Python
class Dog:
    species = '犬科'        # 类属性:所有狗共享

    def __init__(self, name):
        self.name = name    # 实例属性:每只狗自己的

a = Dog('旺财')
b = Dog('小黑')
print(a.name, b.name)
print(a.species, b.species)

输出:

输出
旺财 小黑
犬科 犬科

类属性适合放「这类东西共同的、基本不变的值」,比如所有狗的物种都是犬科。但下面这个坑一定要避开:

Python
class Team:
    members = []            # 类属性:一个列表,所有对象共享!

    def add(self, name):
        self.members.append(name)

t1 = Team()
t2 = Team()
t1.add('小满')
print('t1:', t1.members)
print('t2:', t2.members)   # t2 也多了人!

输出:

Python
t1: ['小满']
t2: ['小满']

t1 加的人,t2 也「看到了」,因为它们共用同一个列表。正确做法是把列表放到 __init__ 里,让每个对象各有一个:

Python
class Team:
    def __init__(self):
        self.members = []   # 实例属性:每个对象各一个

    def add(self, name):
        self.members.append(name)

t1 = Team()
t2 = Team()
t1.add('小满')
print('t1:', t1.members)
print('t2:', t2.members)

输出:

Python
t1: ['小满']
t2: []
类属性实例属性
写在哪类里、方法外__init__ 里用 self.
属于谁整个类共享一份每个对象各一份
适合放共同常量,如 species = '犬科'每个对象不同的数据
可变对象(列表/字典)危险,会被所有对象共享安全,各用各的
拿不准放哪儿时,一律放实例属性(写进 __init__)。类属性只在「所有对象都一样的固定值」时才用。

五、封装:_ 与 __ 两种「私有」约定

封装的意思是:把不想让外面随便碰的数据藏起来,只留几个「正规入口」给外面用。Python 没有强制的访问控制,靠的是两条约定。

单下划线 _name:内部使用,请勿乱碰

一个下划线开头,是给同行的「暗号」:这是内部用的,别在外面直接改。它只是约定,实际仍能访问。

双下划线 __name:改个名藏起来

两个下划线开头的属性,Python 会自动把它改名(叫名称改写),外面用原来的名字就找不到了:

Python
class Account:
    def __init__(self, balance):
        self.__balance = balance

a = Account(100)
print(a.__balance)

运行后会报错(逐字照抄解释器):

报错
AttributeError: 'Account' object has no attribute '__balance'

想读余额,就由类自己提供一个方法当正规入口,顺便还能做检查:

Python
class Account:
    def __init__(self, balance):
        self.__balance = balance

    def get_balance(self):
        return self.__balance

    def save(self, amount):
        if amount <= 0:
            print('金额必须大于 0')
            return
        self.__balance += amount

a = Account(100)
a.save(50)
print('余额:', a.get_balance())
a.save(-30)
print('余额:', a.get_balance())

输出:

输出
余额: 150
金额必须大于 0
余额: 150
写法含义外面能直接访问吗
name公开属性,随便用能
_name内部用(约定)能,但不建议
__name改名藏起来(名称改写)不能(换名字了)
__name__两边都有下划线的是系统预留能,但别自己造
注意:__name(两个下划线开头、末尾没有下划线)才会被改名。像 __init__ 这种前后都有下划线的,是 Python 自己的特殊方法,不会改名,也不要自己乱起这种名字。

AI 助手小贴士|封装是 OOP 的第一性原理,用「余额不能随便改」讲最直白。

打开任意 AI 对话工具(网页版或 App 都可以),试试这样问:用「银行账户余额不能直接改」的例子解释封装:_ 和 __ 前缀各挡住了谁?给一段代码演示两种前缀从外部访问时的不同结果

课程思政|数据安全与法治素养 · 该藏的藏好,只留正规入口

封装讲的是把余额、密码这类内部数据藏起来:_name 是「内部用」的暗号,__name 干脆改名藏好,只留几个方法当正规入口,顺便在门口做检查。这其实就是信息保护的基本思路:能直接拿到,不等于就该随便拿。认真对待每一处「谁能读、谁能改」,将来面对真实数据,你才会本能地问一句「这样访问合规吗」。

六、继承:子类白拿父类的功能

继承让一个类可以「站在另一个类的肩膀上」:把公共的部分写在父类里,子类自动就有,只需再补上自己特有的部分。写法是在类名后面的括号里写上父类名。

Python
class Animal:              # 父类
    def __init__(self, name):
        self.name = name

    def eat(self):
        print(self.name, '在吃东西')

class Dog(Animal):         # 子类:括号里写父类
    def bark(self):
        print(self.name, '汪汪叫')

Dog 只写了 bark,但没有写 __init__ 和 eat——它们都从 Animal 继承过来了:

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

    def eat(self):
        print(self.name, '在吃东西')

class Dog(Animal):
    def bark(self):
        print(self.name, '汪汪叫')

d = Dog('旺财')
d.eat()
d.bark()

输出:

输出
旺财 在吃东西
旺财 汪汪叫
关系叫法例子说明
父类 / 基类Animal被继承的那一个
子类 / 派生类Dog继承别人的那一个
isinstance(对象, 类)isinstance(d, Animal)判断对象属不属于某个类(含父类)
issubclass(子, 父)issubclass(Dog, Animal)判断类之间的继承关系
Python
class Animal:
    def __init__(self, name):
        self.name = name

class Dog(Animal):
    pass

d = Dog('旺财')
print(isinstance(d, Dog))
print(isinstance(d, Animal))
print(issubclass(Dog, Animal))

输出:

输出
True
True
True
继承的收益是「公共代码只写一遍」。如果两个类有一批一模一样的属性和方法,就该把公共部分提出来当父类。别为了继承而继承——没有共同点的两个类硬拉成父子,只会更乱。

七、方法重写与 super()

父类的方法,子类如果想要不一样的版本,可以重写:在子类里定义一个同名方法,用的时候就会用子类这一版。

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

    def speak(self):
        print(self.name, '发出了声音')

class Cat(Animal):
    def speak(self):          # 重写父类的 speak
        print(self.name, '喵喵叫')

a = Animal('某种动物')
a.speak()
c = Cat('小白')
c.speak()

输出:

输出
某种动物 发出了声音
小白 喵喵叫

重写往往还要「在父类的基础上加点东西」,这时用 super() 调用父类的版本。最常见的是子类有自己的初始化,但公共部分仍交给父类做:

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

class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)   # 先把 name 交给父类存好
        self.breed = breed       # 再存子类特有的

    def show(self):
        print(self.name, '是一只', self.breed)

d = Dog('旺财', '金毛')
d.show()

输出:

输出
旺财 是一只 金毛

如果子类定义了 __init__ 却忘了调用 super().__init__(...),父类该存的属性就不会被创建,后面一用就报错:

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

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

d = Dog('旺财', '金毛')
print(d.name)

运行后会报错(逐字照抄解释器):

报错
AttributeError: 'Dog' object has no attribute 'name'
想做什么写法
完全换成新版本子类里定义同名方法即可
在父类版本上「加点料」方法里先调 super().方法名(...),再写自己的
子类有 __init__第一行就 super().__init__(...)
口诀:「子类有自己的 __init__,第一句先 super().__init__(...)」。把父类该做的事让它自己做,子类只补自己的部分。

八、多态:同一句调用,不同表现

多态这个词听着唬人,其实就是一句话:同一句代码,作用在不同对象上,会各自做出不同的反应。

比如写一个 make_sound(animal),里面只写 animal.speak()。传给狗就汪汪,传给猫就喵喵——函数本身一个字都不用改:

Python
class Dog:
    def speak(self):
        print('汪汪!')

class Cat:
    def speak(self):
        print('喵喵~')

class Duck:
    def speak(self):
        print('嘎嘎')

def make_sound(animal):
    animal.speak()

for a in [Dog(), Cat(), Duck()]:
    make_sound(a)

输出:

输出
汪汪!
喵喵~
嘎嘎

多态的好处是「加新类型不用改老代码」。明天再来一个 Pig,只要它也有 speak(),就能直接塞进上面的循环里,make_sound 一个字都不用动。

同一个父类的多态

Python
class Shape:
    def area(self):
        return 0

class Rect(Shape):
    def __init__(self, w, h):
        self.w = w
        self.h = h

    def area(self):
        return self.w * self.h

class Circle(Shape):
    def __init__(self, r):
        self.r = r

    def area(self):
        return round(3.14 * self.r * self.r, 2)

for s in [Rect(3, 4), Circle(2)]:
    print('面积:', s.area())

输出:

输出
面积: 12
面积: 12.56
概念一句话本例中的体现
封装数据和方法打包,内部细节藏起来self.w、area() 写在一个类里
继承子类复用父类的功能Rect(Shape) 白拿 Shape 的东西
多态同一句调用,不同对象不同表现循环里同一句 s.area(),矩形和圆算出不同的值
「三大特性」其实是一件事的三个方面:封装把东西装进类里,继承让类之间能复用,多态让上层代码不用关心具体是哪种对象。懂了这一条,面向对象就算学明白了。

九、常用魔术方法(__str__ / __len__ / __eq__…)

以 __ 开头、__ 结尾的方法叫魔术方法(也叫特殊方法)。你不用直接调用它们,它们是给 Python 的「暗号」——只要你写了,某些内置操作就会自动用上。

最常见的 __init__ 我们已经见过(创建对象时自动调用)。再看几个:

Python
class Book:
    def __init__(self, title, price):
        self.title = title
        self.price = price

    def __str__(self):
        return '《' + self.title + '》 ¥' + str(self.price)

b = Book('Python入门', 39)
print(b)              # 会自动调用 __str__
print(str(b))         # 一样的效果

输出:

输出
《Python入门》 ¥39
《Python入门》 ¥39

没写 __str__ 时,print(b) 会打出一串看不懂的地址。写了之后,打印就变成人话了。

再来两个最实用的:__len__ 让对象能 len(),__eq__ 让对象能用 == 比较「内容是否相等」。

Python
class Playlist:
    def __init__(self, songs):
        self.songs = songs

    def __len__(self):
        return len(self.songs)

    def __eq__(self, other):
        return self.songs == other.songs

p1 = Playlist(['晴天', '稻香', '七里香'])
p2 = Playlist(['晴天', '稻香', '七里香'])
print('歌单长度:', len(p1))
print('内容相同吗:', p1 == p2)

输出:

输出
歌单长度: 3
内容相同吗: True

如果不写 __eq__,两个对象默认比的是「是不是同一个对象」,p1 == p2 会是 False,哪怕内容一模一样。

魔术方法什么时候自动触发例子
__init__创建对象时Hero('小满', 18)
__str__print() / str() 时print(b)
__len__len() 时len(p1)
__eq__用 == 比较时p1 == p2
__lt__用 < 比较时s1 < s2
新手只需先掌握 __init__ 和 __str__ 这两个就够了。写 __str__ 一定要 return 一个字符串,写成 print(...) 会打出 None。

课程思政|诚信与职业操守 · 比的该是内容,不是身份

没写 __eq__ 时,两个对象比的只是「是不是同一个」,哪怕内容一模一样也判为不等;写了它,才改成按内容去比。这一点很像看人:衡量一个人,看的该是他做了什么,而不是名头和标签。写代码也一样,别让「看起来像」蒙混过关,让程序老老实实比内容、对事实负责。

十、@property 与 staticmethod / classmethod

最后认识三个「装饰器」写法,它们能让类的用法更顺。

@property:把方法当属性用

有些「属性」其实是算出来的,比如长方形的面积。用 @property 装饰后,调用时不用写括号,看起来就像在读一个属性:

Python
class Rect:
    def __init__(self, w, h):
        self.w = w
        self.h = h

    @property
    def area(self):
        return self.w * self.h

r = Rect(3, 4)
print(r.area)      # 注意:没有括号

输出:

输出
12

@staticmethod:不需要对象的工具方法

有些方法只是「顺手借个地方放」,和具体对象无关(不碰 self),可以标成静态方法,照样用「类名.方法()」调用:

Python
class MathTool:
    @staticmethod
    def is_even(n):
        return n % 2 == 0

print(MathTool.is_even(4))
print(MathTool.is_even(7))

输出:

输出
True
False

@classmethod:第一个参数是类本身

类方法的第一个参数是 cls(代表类本身),常用来写「换一种方式创建对象」的辅助方法:

Python
class Hero:
    def __init__(self, name, level):
        self.name = name
        self.level = level

    @classmethod
    def from_string(cls, text):
        name, level = text.split('-')
        return cls(name, int(level))

h = Hero.from_string('小满-18')
print(h.name, h.level)

输出:

输出
小满 18
写法第一个参数怎么用典型场景
普通方法self对象.方法()操作这个对象的属性
@staticmethod没有类名.方法()与对象无关的小工具
@classmethodcls(类本身)类名.方法()换一种方式创建对象
@propertyself对象.属性(不带括号)算出来的只读属性
先记住 @property 就够用了——它让你把「算出来的值」包装成属性,用起来自然。@staticmethod / @classmethod 见到能认出来即可,写多了自然会用。

十一、十个基础案例

1. 最简单的英雄类

Python
class Hero:
    def __init__(self, name, power):
        self.name = name
        self.power = power

    def show(self):
        print(self.name, '的战力是', self.power)

h = Hero('小满', 88)
h.show()

输出:

输出
小满 的战力是 88

2. 矩形类:算面积和周长

Python
class Rect:
    def __init__(self, w, h):
        self.w = w
        self.h = h

    def area(self):
        return self.w * self.h

    def perimeter(self):
        return 2 * (self.w + self.h)

r = Rect(3, 4)
print('面积:', r.area())
print('周长:', r.perimeter())

输出:

输出
面积: 12
周长: 14

3. 银行账户:存钱、取钱、看余额

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

    def save(self, amount):
        self.balance += amount

    def withdraw(self, amount):
        if amount > self.balance:
            print('余额不足')
            return
        self.balance -= amount

a = Account('小满', 100)
a.save(50)
a.withdraw(30)
a.withdraw(1000)
print('余额:', a.balance)

输出:

输出
余额不足
余额: 120

4. 计数器类

Python
class Counter:
    def __init__(self):
        self.count = 0

    def add(self):
        self.count += 1

c = Counter()
c.add()
c.add()
c.add()
print('次数:', c.count)

输出:

输出
次数: 3

5. 图书类:自定义打印效果

Python
class Book:
    def __init__(self, title, price):
        self.title = title
        self.price = price

    def __str__(self):
        return '《' + self.title + '》 ¥' + str(self.price)

b = Book('Python入门', 39)
print(b)

输出:

输出
《Python入门》 ¥39

6. 宠物类:继承

Python
class Pet:
    def __init__(self, name):
        self.name = name

    def speak(self):
        print(self.name, '叫了一声')

class Cat(Pet):
    def speak(self):
        print(self.name, ':喵喵~')

class Dog(Pet):
    def speak(self):
        print(self.name, ':汪汪!')

for p in [Cat('小白'), Dog('旺财')]:
    p.speak()

输出:

输出
小白 :喵喵~
旺财 :汪汪!

7. 计算器类:静态方法

Python
class Calculator:
    @staticmethod
    def add(a, b):
        return a + b

    @staticmethod
    def divide(a, b):
        if b == 0:
            return '除数不能为 0'
        return round(a / b, 2)

print(Calculator.add(3, 5))
print(Calculator.divide(10, 3))
print(Calculator.divide(10, 0))

输出:

输出
8
3.33
除数不能为 0

8. 温度类:property 换算

Python
class Temperature:
    def __init__(self, celsius):
        self.celsius = celsius

    @property
    def fahrenheit(self):
        return round(self.celsius * 9 / 5 + 32, 1)

t = Temperature(25)
print('摄氏:', t.celsius)
print('华氏:', t.fahrenheit)

输出:

输出
摄氏: 25
华氏: 77.0

9. 购物车:用列表当属性

Python
class Cart:
    def __init__(self):
        self.items = []

    def add(self, name, price):
        self.items.append({'name': name, 'price': price})

    def total(self):
        return sum(item['price'] for item in self.items)

c = Cart()
c.add('珍珠', 2)
c.add('布丁', 5)
print('共', len(c.items), '件')
print('合计:', c.total(), '元')

输出:

输出
共 2 件
合计: 7 元

10. 员工与经理:继承 + super()

Python
class Employee:
    def __init__(self, name, salary):
        self.name = name
        self.salary = salary

    def show(self):
        print(self.name, '的工资是', self.salary)

class Manager(Employee):
    def __init__(self, name, salary, bonus):
        super().__init__(name, salary)
        self.bonus = bonus

    def show(self):
        print(self.name, '的收入是', self.salary + self.bonus)

m = Manager('林店长', 8000, 2000)
m.show()

输出:

输出
林店长 的收入是 10000
这十个例子覆盖了面向对象最常用的套路:属性在 __init__ 里、行为写成方法、公共部分用继承、打印效果用 __str__、算出来的值用 @property。把这十个例子的类名和数据换一换,就能套到自己的作业里。

十二、常见坑

坑 1:方法忘了写 self

方法定义时第一个参数必须是 self,调用时又不用你传。如果定义时漏了,调用时 Python 会把你传的东西当成多余参数:

Python
class Dog:
    def bark():
        print('汪汪')

d = Dog()
d.bark()

运行后会报错(逐字照抄解释器):

报错
TypeError: Dog.bark() takes 0 positional arguments but 1 was given

坑 2:访问了没创建的属性

属性只有在被赋值之后才存在。忘了在 __init__ 里初始化,一用就报错:

Python
class Hero:
    def __init__(self, name):
        self.name = name

h = Hero('小满')
print(h.power)

运行后会报错(逐字照抄解释器):

报错
AttributeError: 'Hero' object has no attribute 'power'

坑 3:把可变对象写成类属性

类属性是所有对象共享的。如果它是个列表,一个对象改了,全部对象都跟着变(见第四节)。列表、字典这类会变的东西,一律放进 __init__。

Python
class A:
    box = []            # 错:大家共用这一个
    def __init__(self):
        pass

x = A()
y = A()
x.box.append(1)
print('y 的 box:', y.box)

输出:

输出
y 的 box: [1]

坑 4:忘了调用 super().__init__()

子类定义了自己的 __init__,却忘了先让父类初始化,父类那些属性就不存在(见第七节)。

坑 5:以为 == 比的是内容

不写 __eq__ 时,两个对象 == 比的是「是不是同一个」,内容一样也可能是 False:

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

p1 = Point(1, 2)
p2 = Point(1, 2)
print(p1 == p2)
print(p1 is p2)

输出:

输出
False
False

坑 6:调用方法忘了括号

s.show 只是「拿到这个方法」,不会执行;要执行得写 s.show()。忘了括号时不会报错,只是什么都没发生——最容易查半天。

现象原因怎么办
TypeError: bark() takes 0 positional arguments but 1 was given方法定义漏写 self第一个参数补上 self
AttributeError: ... has no attribute 'power'属性没在 __init__ 里创建在 __init__ 里补 self.score = ...
改了对象 A,对象 B 也变了把列表/字典写成了类属性挪进 __init__ 变成实例属性
子类用父类属性报错子类 __init__ 没调 super().__init__()第一行补上它
p1 == p2 莫名是 False没写 __eq__,比的是地址需要就自己写 __eq__
写了 s.show 却毫无反应方法后面漏了括号写成 s.show()

AI 助手小贴士|类设计从「列属性和方法」开始,让 AI 当设计评审。

打开任意 AI 对话工具(网页版或 App 都可以),试试这样问:出 2 道类设计题:①矩形(算面积和周长);②学生(三门成绩、平均分、是否及格)。要求我先列出属性和方法清单,设计完你点评

十三、报错手册

面向对象的报错,绝大部分出在「self」「属性名」「参数个数」这三件事上。下面每条都是实跑出来的原文。

NameError: name 'self' is not defined

原因:在方法里用了 self,但定义方法时忘了把它写进参数表。

Python
class Dog:
    def bark():
        print(self.name)

Dog.bark()   # 不经过对象,直接用类名调用

运行后会报错(逐字照抄解释器):

报错
NameError: name 'self' is not defined

TypeError: bark() takes 0 positional arguments but 1 was given

原因:方法没写 self,但调用时 Python 自动传了一个对象进去,参数对不上。

Python
class Dog:
    def bark():
        print('汪汪')

Dog().bark()

运行后会报错(逐字照抄解释器):

报错
TypeError: Dog.bark() takes 0 positional arguments but 1 was given

AttributeError: 'Hero' object has no attribute 'power'

原因:读了一个从没被赋值的属性。多半是 __init__ 里漏写了,或者拼错了属性名。

Python
class Hero:
    def __init__(self, name):
        self.name = name

h = Hero('小满')
print(h.power)

运行后会报错(逐字照抄解释器):

报错
AttributeError: 'Hero' object has no attribute 'power'

TypeError: Dog.__init__() missing 1 required positional argument: 'name'

原因:创建对象时参数给少了。Dog('旺财') 少了参数,或者写成了 Dog(忘了括号)。

Python
class Dog:
    def __init__(self, name):
        self.name = name

d = Dog()

运行后会报错(逐字照抄解释器):

报错
TypeError: Dog.__init__() missing 1 required positional argument: 'name'

AttributeError: 'Account' object has no attribute '__balance'

原因:__balance 双下划线开头,被 Python 改了名,外面访问不到。要通过类自己提供的普通方法来读(见第五节)。

Python
class Account:
    def __init__(self):
        self.__balance = 100

a = Account()
print(a.__balance)

运行后会报错(逐字照抄解释器):

报错
AttributeError: 'Account' object has no attribute '__balance'

TypeError: 'Hero' object is not callable

原因:把对象当函数调用了——多写了一对括号。用属性直接写 h.name,不用加 ()。

Python
class Hero:
    def __init__(self, name):
        self.name = name

h = Hero('小满')
print(h.name())

运行后会报错(逐字照抄解释器):

报错
TypeError: 'str' object is not callable

NameError: name 'Dog' is not defined

原因:类还没定义就用了,或者类名拼错了(大小写敏感)。类要先定义、后使用。

Python
d = Dog()

class Dog:
    pass

运行后会报错(逐字照抄解释器):

报错
NameError: name 'Dog' is not defined
排错顺序建议:① 看报错最后一行的类型和名字——是 NameError 就查拼写/定义顺序,是 AttributeError 就查属性有没有创建,是 TypeError 就看参数个数;② 再回头看 self 有没有漏。

AI 助手小贴士|self 和 __init__ 是 OOP 的第一道坎。让 AI 从「调用时到底发生了什么」讲起,比从定义背起好懂。

打开任意 AI 对话工具(网页版或 App 都可以),试试这样问:解释 Python 类里的 __init__、self 和实例方法的关系:为什么 s.total() 调用时不用传 self?self 拿到的是什么?用学生成绩单举例

十四、随堂自测(10 题)

点击选项即可判分,选错的题会立刻展开解析。做完可以看到正确率和错题清单,也可以重新答题。

已答 0 / 10 · 正确 0 —

1下面关于「类」和「对象」的说法,正确的是?

  • A. 类是模具/图纸,对象是用它造出来的具体东西
  • B. 类是造出来的东西,对象是图纸
  • C. 类和对象是同义词
  • D. 一个类只能造一个对象
答案:A。类是「模具」,描述这类东西有什么数据、能做什么;对象是用模具造出来的「一块」,可以造很多个,每个的数据还能不同。

2class Hero: 里 __init__ 方法的作用是?

  • A. 给类起名字
  • B. 创建对象时自动执行,用来装初始数据
  • C. 删除对象
  • D. 把类变成函数
答案:B。__init__ 是初始化方法,写 Hero('小满', 18) 创建对象时会被自动调用,用来把初始数据存到 self 上。

3在类的方法里,self 指的是?

  • A. 类本身
  • B. 一个必须手写的参数名,可以随便省略
  • C. 当前正在操作的这个对象
  • D. Python 的保留字,不能改
答案:C。self 就是「当前这个对象」。调用方法时 Python 自动填上,不用你手动传;但定义方法时第一个参数一定要写。

4下面哪种情况会「一个对象改了、其他对象也跟着变」?

  • A. 属性写在 __init__ 里
  • B. 方法里用了 self
  • C. 用 @property 装饰方法
  • D. 把一个空列表写成类属性,然后往里面 append
答案:D。类属性是所有对象共享一份的。列表、字典这种会变的对象写成类属性,一个对象改了其他的都受影响。要各用各的,就放进 __init__。

5子类 Dog(Animal) 里没有定义 eat 方法,写 Dog('旺财').eat() 会怎样?

  • A. 自动去调用父类 Animal 的 eat
  • B. 报错,子类没有这个方法
  • C. 返回 None
  • D. 自动创建一个空方法
答案:A。子类会继承父类的方法,没被重写就能直接用。这正是继承省代码的地方——公共方法只在父类写一遍。

6子类定义了自己的 __init__,想让父类也完成初始化,应该怎么做?

  • A. 什么都不用做,Python 自动调用
  • B. 第一行写 super().__init__(...)
  • C. 把父类的 __init__ 复制过来
  • D. 写 self.__init__(...)
答案:B。子类一旦自己写了 __init__,父类的就不会自动执行了,要显式调用 super().__init__(...)。忘了写,父类该有的属性就不存在,后面一用就报 AttributeError。

7同一句 animal.speak(),传给狗输出「汪汪」、传给猫输出「喵喵」,这体现了哪个特性?

  • A. 封装
  • B. 继承
  • C. 多态
  • D. 重载
答案:C。这就是多态:同一句调用,作用在不同对象上各自做出不同反应。写代码的人不用关心具体是哪种动物,只管调 speak()。

8print(对象) 想打出好看的内容,应该给类写哪个方法?

  • A. __init__
  • B. __del__
  • C. __name__
  • D. __str__
答案:D。写 __str__ 并 return 一个字符串,print(对象) 就会打印它。一定要 return,不能写成 print,否则会打出 None。

9下面哪种写法能让 r.area 不带括号就能算出面积?

  • A. 把 area 写成普通方法
  • B. 在 area 前面加 @staticmethod
  • C. 在 area 前面加 @property
  • D. 把 area 放进 __init__
答案:C。@property 把方法「包装」成属性,调用时不用写括号,看起来就像在读一个普通属性。加 @staticmethod 后仍然要写括号。

10s.name 与 s.name() 的区别是?

  • A. 没有区别
  • B. s.name 取属性值;s.name() 把值当函数调用,通常会报错
  • C. s.name() 才是取属性值
  • D. 两个都会报错
答案:B。属性用「点」直接取,不加括号。多写一对括号等于把值当函数调用,会报 TypeError: 'Hero' object is not callable 之类。方法才需要括号。