Python 面向对象高级编程 --- 定制类
一、整体思路铺垫
Python 里以 **xxx **特殊方法 / 魔法方法
本节核心:给普通类添加魔法方法,让自定义对象用起来像列表、字符串、函数一样灵活
下面逐个讲解常用魔法方法:作用、代码实例、理解思路、配套练习。
二、1. __str__ 和 __repr__(控制对象打印内容)
1. 作用
__str__:给用户看 ,print(对象)时自动执行,自定义打印文本。__repr__:给程序员调试看,直接输入对象名回车时自动执行。- 不定义这两个方法,打印对象只会显示内存地址,看不懂内容。
2. 基础思路
- 不写魔法方法:打印对象 → 输出内存地址(不友好)
- 只写
__str__:print()正常显示,直接写对象名还是地址 - 偷懒写法:
__repr__ = __str__,让两个方法共用一套代码
3. 完整实例
# 定义学生类
class Student:
def __init__(self, name, age):#__init__ 是类的【初始化函数 / 构造方法】专门用来:创建对象的时 #候,自动给对象绑定属性、初始化数据。
self.name = name
self.age = age
# 给 print() 使用
def __str__(self):
return f"学生姓名:{self.name},年龄:{self.age}"
# 调试使用,和 __str__ 共用代码
__repr__ = __str__
# 测试
s = Student("小明", 18)
print(s) # 触发 __str__
s # 交互式终端直接写对象,触发 __repr__
4. 练习题
题目 :定义一个 Book 类,属性有书名、作者。实现 __str__ 和 __repr__,打印格式为:书名:Python入门,作者:廖雪峰。
##代码✅️
# 题目**:定义一个 `Book` 类,属性有书名、作者。实现 `__str__` 和 `__repr__`,打印格式为:`书名:Python入门,作者:廖雪峰`。
class Book:
def __init__(self,book_title,book_author):
self._book_title = book_title
self._book_author = book_author
def __str__(self):
return f'书名:{self._book_title},作者:{self._book_author}'
__repr__ = __str__
book1=Book('python入门','廖雪峰')
print(book1)
##代码 豆包
class Book:
def __init__(self, title, author):
self.title = title
self.author = author
def __str__(self):
return f"书名:{self.title},作者:{self.author}"
__repr__ = __str__
b = Book("Python入门", "廖雪峰")
print(b)
=分割线=理解__str__
一、一句话搞懂:__str__ 到底干嘛的
str 就是:自定义「打印对象时,显示什么文字」
默认情况下:
你打印一个对象,Python 不知道你想看到什么,只能给你一串看不懂的内存地址
写了 __str__告诉 Python:打印这个对象时,输出我指定的通俗易懂内容
二、没有 str 的后果(反面例子)
class Student(object):
def __init__(self, name):
self.name = name
s = Student("小明")
print(s)
<__main__.Student object at 0x...>
✅ 问题:全是机器地址,人类完全看不懂对象存了什么数据
三、加了 str 之后(正面例子)
class Student(object):
def __init__(self, name):
self.name = name
# 专门给 print() 用的:自定义打印内容
def __str__(self):
return f"学生:{self.name}"
s = Student("小明")
print(s)
学生:小明
✅ 效果:打印对象直接显示关键数据,清晰易懂
四、核心精髓(必记)
- 触发时机 :只要你执行
print(对象),自动触发__str__ - 作用 :美化、自定义对象的打印结果
- 返回值 :必须返回字符串
- 只给用户看:面向使用者的友好展示
五、顺带区分你容易混淆的点
__str__→print(对象)用,给普通用户看__repr__→ 终端直接敲对象名用,给程序员调试看
日常写代码,只学 str 就够用 90% 的场景
=分割线=理解print与__str__
print () 函数的内部逻辑,自带了一个固定步骤:打印对象前,先自动执行对象的 str 方法。
通俗大白话类比
print是老板__str__是员工
老板(print)要输出内容,固定流程
必须先喊员工(__str__
不是员工权力比老板大,是老板的规则里,必须调用这个员工。
=分割线=
三、2. __iter__ + __next__(让对象支持 for 循环)
1. 作用
普通自定义对象不能直接放在 for 变量 in 对象
实现这两个方法,就能像列表、元组一样被 for
__iter__:返回迭代对象(一般返回自身self)__next__:每次循环取出下一个值 ,循环结束抛出StopIteration异常
=分割线=理解iter 大白话比喻
for循环 = 自动拿东西的机器__iter__= 告诉 Python:我这个对象可以被循环遍历- 没有
__iter__``for - 有了
__iter__``for
2. 思路
以斐波那契数列(1,1,2,3,5,8...)为例:
- 初始化两个变量保存数列前两位
__iter__返回自己,告诉 Python 这是可迭代对象__next__计算下一个数,设置终止条件,超范围就停止循环
3. 完整实例
class Fib:
def __init__(self):
# 初始化斐波那契前两个数
self.a, self.b = 0, 1
# 返回迭代对象
def __iter__(self):
return self
# 取出循环的下一个值
def __next__(self):
# 计算下一个数
self.a, self.b = self.b, self.a + self.b
# 终止条件:数值大于100就停止循环
if self.a > 100:
raise StopIteration()
return self.a
# 遍历自定义对象
for num in Fib():
print(num, end=" ")
4. 练习题
题目 :创建一个 Count 类,实现 for 循环遍历,依次输出 1~20 的数字。
##代码✅️
# 题目**:创建一个 `Count` 类,实现 `for` 循环遍历,依次输出 1~20 的数字。
class Count:
def __init__(self):
self.count = 0
def __str__(self):
return str(self.count)
# 少写了iter for循环不认识
def __iter__(self):
return self
def __next__(self):
self.count += 1
if self.count >20:#不是大于等于20 会漏掉20,应该是大于20
raise StopIteration
return self.count
for i in Count():
print(i)
#代码 豆包
class Count:
def __init__(self):
self.num = 0
def __iter__(self):
return self
def __next__(self):
self.num += 1
if self.num > 20:
raise StopIteration()
return self.num
for i in Count():
print(i, end=" ")
四、3. __getitem__(让对象支持下标 / 切片取值)
1. 作用
实现后,对象可以像列表一样:
- 用
对象[索引]取单个元素 - 用
对象[起始:结束]做切片
=分割线=理解__getitem__ 大白话比喻
-
列表
list能list[0]取值 -
你的对象 本来不行
-
写了
__getitem__→
你的对象瞬间拥有了列表的下标功能!
=分割线=切片的规则
规则1 包前不包后
起始:结束 是取下标 ≥起始 且 下标 < 结束,包前不包后。
0,5,取 列表0-列表5
即 列表0 列表1 列表2 列表3 列表4
因为包前不包后 所以不包含列表5
规则2 开头不写,默认从 0 开始
:5 = 0:5 意思:从最开头取到 5
规则3 结尾不写,默认取到最后
2: 意思:从 2 开始,取到完
规则4 两个都不写,就是全部
: 意思:取所有数字
切片的例子
c0:5→ 取0~4
c:5→ 取 开头~4
c2:→ 取2 ~最后
c:→ 取全部
2. 思路
__getitem__(self, n) 里的参数 n 分两种情况:
n是整数:代表索引,按位置取值n是切片对象 (slice) :代表[x:y]切片,遍历拼接列表返回
3. 完整实例(斐波那契数列升级版)
class Fib:
def __getitem__(self, n):
# 情况1:传入整数索引
if isinstance(n, int): ##isinstance( 要判断的变量 , 类型 )
a, b = 1, 1
for _ in range(n):
a, b = b, a + b
return a
# 情况2:传入切片
if isinstance(n, slice):##isinstance( 要判断的变量 , 类型 )
start = n.start
stop = n.stop
# 切片没写起始,默认从0开始
if start is None:
start = 0
a, b = 1, 1
res = []
for x in range(stop):
if x >= start:
res.append(a)
a, b = b, a + b
return res
# 测试
f = Fib()
print(f[0]) # 索引取值
print(f[1:6]) # 切片取值
4. 练习题
题目 :改造上面的 Count 类,实现 __getitem__,支持下标取值和切片,模拟 1~30 的数字序列。
##代码 ✅️
# =================
# 题目**:创建一个 `Count` 类,实现 `for` 循环遍历,依次输出 1~20 的数字。
# 题目**:改造上面的 `Count` 类,实现 `__getitem__`,支持下标取值和切片,模拟 1~30 的数字序列。
class Count:
def __init__(self):
self.count = 0
def __str__(self):
return str(self.count)
# 少写了iter for循环不认识
def __iter__(self):
return self
def __next__(self):
self.count += 1
if self.count > 20: # 不是大于等于20 会漏掉20,应该是大于20
raise StopIteration
return self.count
#== 新增下标取值
## 注释: __getitem__ 就是专门处理:下标取值 + 切片取值的方法!
def __getitem__(self, item):## item可能是下标 也可能是切片
##取整数
if isinstance(item, int):
return item+1
##取切片
if isinstance(item, slice): #slice = 切片
start=item.start or 0 # 结束位置:没写就用 30(模拟1~30)
stop=item.stop or 30 # 结束位置:没写就用 30(模拟1~30)
result=[] ## 创建一个列表
for i in range(start,stop): # 循环生成一段数字
result.append(i+1)
return result
# 测试:输出 1~20
for num in Count():
print(num)
#代码 豆包
class Count:
def __getitem__(self, n):
if isinstance(n, int):
# 索引从0开始,对应数字+1
return n + 1
if isinstance(n, slice):
start = n.start if n.start else 0
stop = n.stop
res = []
for i in range(start, stop):
res.append(i + 1)
return res
c = Count()
print(c[5])
print(c[2:8])
注释
#注释1:下标从 0 开始 → 通过公式 n + 1 → 生成数字 1~3
#注释2 :切片 = 从一堆数字里,截取【一段】出来!
下标取值 :拿一个 数字 → c[5]
切片 :拿一整段 数字 → c[0:5]
五、4. __getattr__(动态处理不存在的属性)
1. 作用
当你调用对象不存在的属性 / 方法 时,Python 会自动执行 __getattr__,不会直接报错,可以动态返回内容。
注意:已有属性不会触发,只处理不存在的属性。
2. 思路
- 正常属性直接使用,不进这个方法
- 访问不存在属性 → 进入
__getattr__(self, 属性名) - 判断属性名,返回对应值 / 函数;不匹配就主动抛
AttributeError模拟原生报错
=分割线=理解getattr__
一句话总结:
当你访问一个 "不存在的属性" 时,Python 会自动调用 getattr 方法。
## 例子
class Person:
def __init__(self):
self.name = "小明"
# 👇 访问不存在的属性时,自动触发这里
def __getattr__(self, item):
return f"你访问的 {item} 属性不存在!"
p = Person()
print(p.name) # 正常
print(p.age) # 不存在 → 自动走 __getattr__
print(p.abc) # 不存在 → 自动走 __getattr__
3. 基础实例
class Student:
def __init__(self):
self.name = "小红"
# 处理不存在的属性
def __getattr__(self, attr):
# 判断属性名
if attr == "score":
return 95
if attr == "age":
# 返回函数
return lambda: 20
# 不匹配的属性,抛出标准错误
raise AttributeError(f"没有属性:{attr}")
# 测试
s = Student()
print(s.name) # 已有属性,正常使用
print(s.score) # 不存在属性,触发 __getattr__
print(s.age()) # 调用返回的函数
# print(s.height) # 会报错
4. 进阶实战(链式调用 URL)
利用 __getattr__ 实现 API 路径拼接,这是实际开发常用场景:
class Chain:
def __init__(self, path=""):
self._path = path
def __getattr__(self, name):
# 拼接路径,返回新对象实现链式调用
return Chain(f"{self._path}/{name}")
def __str__(self):
return self._path
__repr__ = __str__
# 链式调用拼接URL
print(Chain().user.info.address)
# 输出:/user/info/address
5. 练习题
题目 :定义 Person 类,已有属性 name。当访问不存在的 gender 返回 女,访问 height 返回函数(返回 165),其他属性正常报错。
##代码
# 题目**:定义 `Person` 类,已有属性 `name`。当访问不存在的 `gender` 返回 `女`,访问 `height` 返回函数(返回 165),其他属性正常报错。
class Person:
def __init__(self, name):
self.name = name
def __getattr__(self, item):
if item == 'gender':
return '女'
if item == 'height':
# return '165' 错误点❌️:访问 `height` 返回函数(返回 165),我返回了字符串'165'
def func():
return 165
return func
# else: #其他属性
# return f'{item}属性不存在'
raise AttributeError(f'{item}属性不存在')
p= Person('xiaoming')
print(p.name)
print(p.gender)
print(p.height())
#代码 豆包
class Person:
def __init__(self):
self.name = "丽丽"
def __getattr__(self, attr):
if attr == "gender":
return "女"
if attr == "height":
return lambda: 165
raise AttributeError(f"无属性 {attr}")
p = Person()
print(p.name)
print(p.gender)
print(p.height())
六、5. __call__(让对象像函数一样直接调用)
1. 作用
普通对象:只能调用 对象.方法()定义 后:可以直接写 ,把。
搭配 callable() 函数:判断一个东西是否能被 () 调用(可调用对象)。
2. 思路
- 在类中定义
def __call__(self, 可选参数) - 实例化对象后,直接
对象(参数)就会触发该方法
=分割线=理解__call__
call 让一个 对象能像 函数一样加括号 () 调用!
你创建了一个对象,本来只能这样用:
p = Person()
p.属性
p.方法()
但有了 __call__,你可以直接把对象当函数用:
p() # 👈 加括号就能跑!这就是 __call__ 的作用
3. 完整实例
#代码 豆包
class Student:
def __init__(self, name):
self.name = name
# 对象被直接调用时执行
def __call__(self, msg="你好"):
print(f"我是{self.name},{msg}")
# 测试
s = Student("小华")
s() # 无参调用
s("加油学习") # 传参调用
# 判断是否为可调用对象
print(callable(s)) # True 对象可调用
print(callable([1,2]))# False 列表不能被()调用
4. 练习题
题目 :定义 Dog 类,初始化传入狗狗名字。实现 __call__,调用 dog() 时输出 狗狗名字 + 汪汪叫。
# 题目**:定义 `Dog` 类,初始化传入狗狗名字。实现 `__call__`,调用 `dog()` 时输出 `狗狗名字 + 汪汪叫`。
class Dog:
def __init__(self, name,):
self.name = name
def __call__(self,msg='汪汪叫'):
print(f'{self.name}{msg}')
dog1 = Dog('小白')
dog1()
class Dog:
def __init__(self, dog_name):
self.dog_name = dog_name
def __call__(self):
print(f"{self.dog_name}:汪汪叫")
d = Dog("旺财")
d()
=分割线=print用法
一句话死记:
print (函数 / 方法 () ) → 只打印它 return 回来的内容!
如果这个方法没有写 return ,Python 就默认返回 None,所以 print 就打印 None!
七、整体知识点总结(小白速记)
表格
| 魔法方法 | 核心作用 | 使用场景 |
|---|---|---|
__str__ / __repr__ |
自定义对象打印内容 | print(对象)、调试查看对象 |
__iter__ + __next__ |
让对象支持 for 循环 | 自定义可遍历序列 |
__getitem__ |
支持下标、切片取值 | 模仿列表取值操作 |
__getattr__ |
动态处理不存在的属性 | 链式调用、动态接口封装 |
__call__ |
对象像函数一样调用 | 把对象当成函数使用 |
核心逻辑:魔法方法不用手动调用,Python 在对应场景自动触发,以此扩展类的功能。