python学习笔记 | 11.4、面向对象高级编程-定制类

Python 面向对象高级编程 --- 定制类

一、整体思路铺垫

Python 里以 **xxx **特殊方法 / 魔法方法

本节核心:给普通类添加魔法方法,让自定义对象用起来像列表、字符串、函数一样灵活

下面逐个讲解常用魔法方法:作用、代码实例、理解思路、配套练习。


二、1. __str____repr__(控制对象打印内容)

1. 作用

  • __str__给用户看print(对象) 时自动执行,自定义打印文本。
  • __repr__给程序员调试看,直接输入对象名回车时自动执行。
  • 不定义这两个方法,打印对象只会显示内存地址,看不懂内容。

2. 基础思路

  1. 不写魔法方法:打印对象 → 输出内存地址(不友好)
  2. 只写 __str__print() 正常显示,直接写对象名还是地址
  3. 偷懒写法:__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)

学生:小明

✅ 效果:打印对象直接显示关键数据,清晰易懂

四、核心精髓(必记)
  1. 触发时机 :只要你执行 print(对象),自动触发 __str__
  2. 作用 :美化、自定义对象的打印结果
  3. 返回值 :必须返回字符串
  4. 只给用户看:面向使用者的友好展示
五、顺带区分你容易混淆的点
  • __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...)为例:

  1. 初始化两个变量保存数列前两位
  2. __iter__ 返回自己,告诉 Python 这是可迭代对象
  3. __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__ 大白话比喻

  • 列表 listlist[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 分两种情况:

  1. n整数:代表索引,按位置取值
  2. 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. 思路

  1. 正常属性直接使用,不进这个方法
  2. 访问不存在属性 → 进入 __getattr__(self, 属性名)
  3. 判断属性名,返回对应值 / 函数;不匹配就主动抛 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. 思路

  1. 在类中定义 def __call__(self, 可选参数)
  2. 实例化对象后,直接 对象(参数) 就会触发该方法

=分割线=理解__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 在对应场景自动触发,以此扩展类的功能

相关推荐
CanCanCanedFish1 小时前
Transformer论文阅读笔记:从注意力机制到革命性架构的启示
论文阅读·笔记·transformer
站大爷IP1 小时前
Python闭包变量作用域踩坑实录,原来我们都想错了
python
zzj_2626101 小时前
实验七 Python 文件操作与异常处理
开发语言·python
wangcheng3031 小时前
LLMOps入门:高效管理大型语言模型
笔记
菜到离谱但坚持1 小时前
零门槛学LangChain:AI开发从入门到实战
python·langchain·prompt·rag
ZzYH221 小时前
文献阅读 260602-A universal scaling law of intra-urban inequality
笔记
Niyy_1 小时前
WASM 的使用笔记
jvm·笔记·wasm
databook1 小时前
一次函数图像工厂:用 SymPy 自动生成 y=kx+b 对比动画
python·数学·动效
测试老哥1 小时前
接口测试详解
自动化测试·软件测试·python·测试工具·职场和发展·测试用例·接口测试