一、为什么需要「类」(把数据和操作打包)
前面学过列表、字典、函数。真想写个「游戏角色管理」的小程序,你多半会这样描述一个角色:
hero = {'名字': '小满', '等级': 18, '战力': 88}
一个角色没问题。可当你有 30 个角色,还要给每个人「出场问候」「算段位」时,就会发现一件别扭的事:数据(名字、战力)装在字典里,处理数据的函数却散落在外面。
def 出场问候(角色):
print('登场!我是', 角色['名字'])
def 算段位(角色):
return '王者' if 角色['战力'] >= 90 else '青铜'
这么写能跑,但有两个隐患:① 字典的键全靠字符串,写成 角色['名子'] 要等到运行时才报错;② 数据和操作它的函数没有绑在一起,谁都能随手改,代码一长就乱。
类(class)要解决的正是这件事:把「这一类东西有哪些数据」和「它们能做什么」打包成一个整体,然后像用模具一样,造出一个一个具体的对象。
| 说法 | 英文 | 一句话 |
|---|---|---|
| 类 | class | 模具/图纸,描述「这类东西长什么样、能做什么」 |
| 对象(实例) | object / instance | 用模具造出来的一块,各自装着不同的数据 |
| 属性 | attribute | 对象身上存的数据,如 name、age |
| 方法 | method | 写在类里的函数,专门处理这类对象 |
打个比方:类=月饼模具,对象=压出来的一块块月饼。模具只有一个,月饼可以压很多块;每块月饼的馅儿(属性值)还能各不相同。
二、定义类与创建对象(class、__init__、self)
定义类的关键字是 class,类名习惯用「大驼峰」写法(每个单词首字母大写,如 BankAccount)。
class Hero:
def __init__(self, name, level):
self.name = name
self.level = level
短短四行,拆开看是三件事:
class Hero:—— 声明「我要定义一类叫Hero的东西」,后面缩进的都归它管。def __init__(self, name, level):—— 初始化方法(init前后各两个下划线)。创建对象时会自动执行,用来把初始数据装进去。self.name = name—— 把传进来的值,存到这个对象自己身上,名字叫name。
创建对象不用 new,直接写「类名(参数)」就行:
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。
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.属性名:
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 实例属性(最容易踩的坑)
属性其实分两种,位置不一样,含义也不一样:
- 实例属性:写在
__init__里,self.xxx = ...。每个对象各有一份。 - 类属性:直接写在类里面、方法外面。所有对象共享同一份。
class Dog:
species = '犬科' # 类属性:所有狗共享
def __init__(self, name):
self.name = name # 实例属性:每只狗自己的
a = Dog('旺财')
b = Dog('小黑')
print(a.name, b.name)
print(a.species, b.species)
输出:
旺财 小黑
犬科 犬科
类属性适合放「这类东西共同的、基本不变的值」,比如所有狗的物种都是犬科。但下面这个坑一定要避开:
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 也多了人!
输出:
t1: ['小满']
t2: ['小满']
t1 加的人,t2 也「看到了」,因为它们共用同一个列表。正确做法是把列表放到 __init__ 里,让每个对象各有一个:
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)
输出:
t1: ['小满']
t2: []
| 类属性 | 实例属性 | |
|---|---|---|
| 写在哪 | 类里、方法外 | __init__ 里用 self. |
| 属于谁 | 整个类共享一份 | 每个对象各一份 |
| 适合放 | 共同常量,如 species = '犬科' | 每个对象不同的数据 |
| 可变对象(列表/字典) | 危险,会被所有对象共享 | 安全,各用各的 |
__init__)。类属性只在「所有对象都一样的固定值」时才用。五、封装:_ 与 __ 两种「私有」约定
封装的意思是:把不想让外面随便碰的数据藏起来,只留几个「正规入口」给外面用。Python 没有强制的访问控制,靠的是两条约定。
单下划线 _name:内部使用,请勿乱碰
一个下划线开头,是给同行的「暗号」:这是内部用的,别在外面直接改。它只是约定,实际仍能访问。
双下划线 __name:改个名藏起来
两个下划线开头的属性,Python 会自动把它改名(叫名称改写),外面用原来的名字就找不到了:
class Account:
def __init__(self, balance):
self.__balance = balance
a = Account(100)
print(a.__balance)
运行后会报错(逐字照抄解释器):
AttributeError: 'Account' object has no attribute '__balance'
想读余额,就由类自己提供一个方法当正规入口,顺便还能做检查:
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 干脆改名藏好,只留几个方法当正规入口,顺便在门口做检查。这其实就是信息保护的基本思路:能直接拿到,不等于就该随便拿。认真对待每一处「谁能读、谁能改」,将来面对真实数据,你才会本能地问一句「这样访问合规吗」。
六、继承:子类白拿父类的功能
继承让一个类可以「站在另一个类的肩膀上」:把公共的部分写在父类里,子类自动就有,只需再补上自己特有的部分。写法是在类名后面的括号里写上父类名。
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 继承过来了:
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) | 判断类之间的继承关系 |
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()
父类的方法,子类如果想要不一样的版本,可以重写:在子类里定义一个同名方法,用的时候就会用子类这一版。
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() 调用父类的版本。最常见的是子类有自己的初始化,但公共部分仍交给父类做:
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__(...),父类该存的属性就不会被创建,后面一用就报错:
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()。传给狗就汪汪,传给猫就喵喵——函数本身一个字都不用改:
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 一个字都不用动。
同一个父类的多态
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__ 我们已经见过(创建对象时自动调用)。再看几个:
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__ 让对象能用 == 比较「内容是否相等」。
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 装饰后,调用时不用写括号,看起来就像在读一个属性:
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),可以标成静态方法,照样用「类名.方法()」调用:
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(代表类本身),常用来写「换一种方式创建对象」的辅助方法:
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 | 没有 | 类名.方法() | 与对象无关的小工具 |
@classmethod | cls(类本身) | 类名.方法() | 换一种方式创建对象 |
@property | self | 对象.属性(不带括号) | 算出来的只读属性 |
@property 就够用了——它让你把「算出来的值」包装成属性,用起来自然。@staticmethod / @classmethod 见到能认出来即可,写多了自然会用。十一、十个基础案例
1. 最简单的英雄类
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. 矩形类:算面积和周长
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. 银行账户:存钱、取钱、看余额
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. 计数器类
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. 图书类:自定义打印效果
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. 宠物类:继承
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. 计算器类:静态方法
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 换算
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. 购物车:用列表当属性
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()
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 会把你传的东西当成多余参数:
class Dog:
def bark():
print('汪汪')
d = Dog()
d.bark()
运行后会报错(逐字照抄解释器):
TypeError: Dog.bark() takes 0 positional arguments but 1 was given
坑 2:访问了没创建的属性
属性只有在被赋值之后才存在。忘了在 __init__ 里初始化,一用就报错:
class Hero:
def __init__(self, name):
self.name = name
h = Hero('小满')
print(h.power)
运行后会报错(逐字照抄解释器):
AttributeError: 'Hero' object has no attribute 'power'
坑 3:把可变对象写成类属性
类属性是所有对象共享的。如果它是个列表,一个对象改了,全部对象都跟着变(见第四节)。列表、字典这类会变的东西,一律放进 __init__。
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:
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,但定义方法时忘了把它写进参数表。
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 自动传了一个对象进去,参数对不上。
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__ 里漏写了,或者拼错了属性名。
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(忘了括号)。
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 改了名,外面访问不到。要通过类自己提供的普通方法来读(见第五节)。
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,不用加 ()。
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
原因:类还没定义就用了,或者类名拼错了(大小写敏感)。类要先定义、后使用。
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 题)
点击选项即可判分,选错的题会立刻展开解析。做完可以看到正确率和错题清单,也可以重新答题。
1下面关于「类」和「对象」的说法,正确的是?
- A. 类是模具/图纸,对象是用它造出来的具体东西
- B. 类是造出来的东西,对象是图纸
- C. 类和对象是同义词
- D. 一个类只能造一个对象
2class Hero: 里 __init__ 方法的作用是?
- A. 给类起名字
- B. 创建对象时自动执行,用来装初始数据
- C. 删除对象
- D. 把类变成函数
__init__ 是初始化方法,写 Hero('小满', 18) 创建对象时会被自动调用,用来把初始数据存到 self 上。3在类的方法里,self 指的是?
- A. 类本身
- B. 一个必须手写的参数名,可以随便省略
- C. 当前正在操作的这个对象
- D. Python 的保留字,不能改
self 就是「当前这个对象」。调用方法时 Python 自动填上,不用你手动传;但定义方法时第一个参数一定要写。4下面哪种情况会「一个对象改了、其他对象也跟着变」?
- A. 属性写在
__init__里 - B. 方法里用了
self - C. 用
@property装饰方法 - D. 把一个空列表写成类属性,然后往里面
append
__init__。5子类 Dog(Animal) 里没有定义 eat 方法,写 Dog('旺财').eat() 会怎样?
- A. 自动去调用父类
Animal的eat - B. 报错,子类没有这个方法
- C. 返回 None
- D. 自动创建一个空方法
6子类定义了自己的 __init__,想让父类也完成初始化,应该怎么做?
- A. 什么都不用做,Python 自动调用
- B. 第一行写
super().__init__(...) - C. 把父类的
__init__复制过来 - D. 写
self.__init__(...)
__init__,父类的就不会自动执行了,要显式调用 super().__init__(...)。忘了写,父类该有的属性就不存在,后面一用就报 AttributeError。7同一句 animal.speak(),传给狗输出「汪汪」、传给猫输出「喵喵」,这体现了哪个特性?
- A. 封装
- B. 继承
- C. 多态
- D. 重载
speak()。8print(对象) 想打出好看的内容,应该给类写哪个方法?
- A.
__init__ - B.
__del__ - C.
__name__ - D.
__str__
__str__ 并 return 一个字符串,print(对象) 就会打印它。一定要 return,不能写成 print,否则会打出 None。9下面哪种写法能让 r.area 不带括号就能算出面积?
- A. 把
area写成普通方法 - B. 在
area前面加@staticmethod - C. 在
area前面加@property - D. 把
area放进__init__
@property 把方法「包装」成属性,调用时不用写括号,看起来就像在读一个普通属性。加 @staticmethod 后仍然要写括号。10s.name 与 s.name() 的区别是?
- A. 没有区别
- B.
s.name取属性值;s.name()把值当函数调用,通常会报错 - C.
s.name()才是取属性值 - D. 两个都会报错
TypeError: 'Hero' object is not callable 之类。方法才需要括号。