目 录CONTENT

文章目录

Python(三十一) 类的特殊方法(魔术方法)详解

Python(三十一) 类的特殊方法(魔术方法)详解

1. 什么是特殊方法

Python 中有一类方法,名字前后都有两个下划线。

例如:

__init__
__str__
__len__
__add__

这种方法通常叫做特殊方法,也常被叫做魔术方法。

英文中常叫:

special method
magic method
dunder method

其中 dunder 是 double underscore 的缩写,意思是“双下划线”。

所以:

__init__

可以读作:

dunder init

通俗地说:

特殊方法是 Python 预留的一些方法名。
当对象遇到某些操作时,Python 会自动调用这些方法。

例如:

print(obj)

可能会自动调用:

obj.__str__()

再比如:

len(obj)

可能会自动调用:

obj.__len__()

2. 为什么叫魔术方法

因为这些方法通常不需要我们手动调用。

它们会在特定语法或内置函数中被 Python 自动调用。

例如:

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


stu = Student("张三")

当执行:

Student("张三")

Python 会自动调用:

__init__()

所以初学者看起来像“自动发生了什么事情”,因此常叫魔术方法。

但它不是魔法,本质上只是 Python 的固定规则。

3. 特殊方法的核心作用

特殊方法的作用是:

让自定义类的对象也能像 Python 内置类型一样使用。

例如,列表可以这样用:

nums = [1, 2, 3]

print(len(nums))
print(nums[0])
print(nums + [4, 5])

如果我们自己定义一个类,也希望它支持:

len(obj)
obj[0]
obj + other
print(obj)

就可以通过特殊方法实现。

也就是说:

特殊方法让对象支持 Python 的内置语法和内置函数。

4. 特殊方法的命名特点

特殊方法一般长这样:

__方法名__

特点:

  1. 方法名前面有两个下划线。
  2. 方法名后面也有两个下划线。
  3. 名字是 Python 规定好的,不能随便编。

例如:

__init__
__str__
__repr__
__len__
__getitem__
__setitem__
__add__
__eq__

注意:

特殊方法不是自己随便起名的。
必须使用 Python 已经规定好的名字,Python 才会自动调用。

错误示例:

def __show__():
    pass

如果 Python 没有规定 __show__ 这个特殊方法,那么它只是一个普通方法名,不会自动触发特殊行为。

5. 特殊方法需要手动调用吗

一般不建议手动调用特殊方法。

例如,不推荐:

stu.__str__()

更推荐:

str(stu)
print(stu)

不推荐:

obj.__len__()

更推荐:

len(obj)

教学记忆:

特殊方法通常由 Python 自动调用。
我们一般使用对应的语法或内置函数触发它。

6. 常见特殊方法分类

常见特殊方法可以分成这些类别:

类别 常见方法 作用
创建和初始化 __new____init____del__ 控制对象创建、初始化、销毁
字符串表示 __str____repr__ 控制对象打印和显示
长度和布尔值 __len____bool__ 支持 len() 和真假判断
运算符 __add____sub____mul__ 支持 +-*
比较运算 __eq____lt____gt__ 支持 ==<>
容器操作 __getitem____setitem____contains__ 支持下标、赋值、in
迭代器 __iter____next__ 支持 for 循环
函数调用 __call__ 让对象可以像函数一样调用
上下文管理 __enter____exit__ 支持 with 语句
属性访问 __getattr____setattr__ 控制属性访问和设置

下面逐类讲解。

7. init 初始化方法

__init__ 是最常见的特殊方法。

它在对象创建后自动执行,通常用来初始化对象属性。

示例:

class Student:
    def __init__(self, name, age):
        self.name = name
        self.age = age


stu = Student("张三", 18)

print(stu.name)
print(stu.age)

输出:

张三
18

当执行:

Student("张三", 18)

Python 会自动调用:

__init__(self, name, age)

教学理解:

__init__ 负责给新对象准备初始数据。

8. init 的注意事项

8.1 init 不是构造对象本身

很多初学者会说:

__init__ 是构造方法。

在教学初期这样理解问题不大,但更准确地说:

__init__ 是初始化方法。
对象已经创建好了,它负责初始化对象属性。

真正负责创建对象的是 __new__

不过初学阶段重点掌握 __init__ 即可。

8.2 init 不能返回普通值

错误写法:

class Student:
    def __init__(self, name):
        self.name = name
        return name

__init__ 不能返回非 None 的值。

正确写法:

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

8.3 init 名字不要写错

正确:

def __init__(self):
    pass

错误:

def _init_(self):
    pass

错误:

def init(self):
    pass

__init__ 前后都是两个下划线。

9. new 创建对象方法

__new__ 是真正负责创建对象的特殊方法。

它在 __init__ 之前执行。

示例:

class Demo:
    def __new__(cls):
        print("__new__ 被调用")
        return super().__new__(cls)

    def __init__(self):
        print("__init__ 被调用")


obj = Demo()

输出:

__new__ 被调用
__init__ 被调用

执行顺序:

先执行 __new__,创建对象。
再执行 __init__,初始化对象。

教学建议:

初学阶段知道 __new__ 比 __init__ 更早执行即可。
一般业务代码很少需要自己重写 __new__。

10. new 的常见用途

__new__ 通常在这些场景中出现:

控制对象创建过程
实现单例模式
继承不可变类型时定制对象

简单了解即可。

示例:非常简化的单例模式。

class Singleton:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance


a = Singleton()
b = Singleton()

print(a is b)

输出:

True

说明:

a 和 b 指向同一个对象。

这个例子适合了解,不建议初学阶段重点展开。

11. del 析构方法

__del__ 在对象即将被销毁时可能被调用。

示例:

class Demo:
    def __del__(self):
        print("对象被销毁")


obj = Demo()
del obj

输出可能是:

对象被销毁

注意:

__del__ 的调用时机不一定总是容易预测。

因此,不建议把重要业务逻辑依赖在 __del__ 中。

例如文件关闭、网络连接关闭,更推荐使用 with 语句和上下文管理器。

12. str 面向用户的字符串

__str__ 用来控制对象转换成字符串时的结果。

常见触发方式:

print(obj)
str(obj)

不定义 __str__ 时:

class Student:
    def __init__(self, name, age):
        self.name = name
        self.age = age


stu = Student("张三", 18)
print(stu)

输出类似:

<__main__.Student object at 0x00000123456789AB>

这对人不太友好。

定义 __str__ 后:

class Student:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
        return f"学生:{self.name},年龄:{self.age}"


stu = Student("张三", 18)
print(stu)

输出:

学生:张三,年龄:18

13. str 的注意事项

13.1 str 必须返回字符串

错误写法:

class Student:
    def __str__(self):
        return 123

会报错。

正确写法:

class Student:
    def __str__(self):
        return "这是一个学生对象"

13.2 str 不应该直接 print

错误写法:

class Student:
    def __str__(self):
        print("学生对象")

问题:

__str__ 应该返回字符串,不是直接打印。

正确写法:

class Student:
    def __str__(self):
        return "学生对象"

14. repr 面向开发者的字符串

__repr__ 也用于对象的字符串表示。

它更偏向开发者调试使用。

常见触发方式:

repr(obj)
在交互环境中直接输入 obj
列表中显示对象

示例:

class Student:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __repr__(self):
        return f"Student(name={self.name!r}, age={self.age!r})"


stu = Student("张三", 18)

print(repr(stu))
print([stu])

输出:

Student(name='张三', age=18)
[Student(name='张三', age=18)]

这里的 !r 表示使用 repr() 格式化。

15. strrepr 的区别

方法 面向谁 常见触发 风格
__str__ 普通用户 print()str() 友好、易读
__repr__ 开发者 repr()、调试、容器显示 准确、明确

示例:

class Student:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
        return f"{self.name},{self.age}岁"

    def __repr__(self):
        return f"Student(name={self.name!r}, age={self.age!r})"


stu = Student("张三", 18)

print(str(stu))
print(repr(stu))

输出:

张三,18岁
Student(name='张三', age=18)

教学建议:

如果只讲一个,先讲 __str__。
如果要讲调试和对象列表显示,再讲 __repr__。

16. len 支持 len()

如果希望自定义对象支持 len(),可以定义 __len__

示例:

class Team:
    def __init__(self, members):
        self.members = members

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


team = Team(["张三", "李四", "王五"])

print(len(team))

输出:

3

当执行:

len(team)

Python 会自动调用:

team.__len__()

17. len 的注意事项

__len__ 必须返回非负整数。

正确:

def __len__(self):
    return 3

错误:

def __len__(self):
    return -1

错误:

def __len__(self):
    return "3"

__len__ 常用于表示:

容器中元素的数量
队伍人数
购物车商品数
书架图书数

18. bool 支持真假判断

如果希望对象可以参与真假判断,可以定义 __bool__

示例:

class Cart:
    def __init__(self, items):
        self.items = items

    def __bool__(self):
        return len(self.items) > 0


cart1 = Cart(["苹果", "牛奶"])
cart2 = Cart([])

print(bool(cart1))
print(bool(cart2))

if cart1:
    print("购物车有商品")

if not cart2:
    print("购物车为空")

输出:

True
False
购物车有商品
购物车为空

19. boollen 的关系

如果没有定义 __bool__,但定义了 __len__,Python 会用长度判断真假。

长度为 0 时是假。

长度不为 0 时是真。

示例:

class Cart:
    def __init__(self, items):
        self.items = items

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


cart1 = Cart(["苹果"])
cart2 = Cart([])

print(bool(cart1))
print(bool(cart2))

输出:

True
False

教学记忆:

有 __bool__,优先用 __bool__。
没有 __bool__,可以用 __len__ 判断真假。

20. eq 支持 ==

默认情况下,两个对象用 == 比较,通常比较的是对象身份。

示例:

class Student:
    def __init__(self, name, age):
        self.name = name
        self.age = age


stu1 = Student("张三", 18)
stu2 = Student("张三", 18)

print(stu1 == stu2)

输出:

False

虽然属性一样,但它们是两个不同对象。

如果希望根据属性判断是否相等,可以定义 __eq__

class Student:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __eq__(self, other):
        return self.name == other.name and self.age == other.age


stu1 = Student("张三", 18)
stu2 = Student("张三", 18)

print(stu1 == stu2)

输出:

True

21. eq 的注意事项

__eq__ 中最好判断 other 的类型。

不推荐:

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

如果 other 不是 Student,可能报错。

更推荐:

class Student:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __eq__(self, other):
        if not isinstance(other, Student):
            return False
        return self.name == other.name and self.age == other.age

示例:

stu = Student("张三", 18)

print(stu == "张三")

输出:

False

22. 比较运算特殊方法

常见比较运算方法:

运算符 特殊方法 含义
== __eq__ 等于
!= __ne__ 不等于
< __lt__ 小于
<= __le__ 小于等于
> __gt__ 大于
>= __ge__ 大于等于

示例:按照成绩比较学生。

class Student:
    def __init__(self, name, score):
        self.name = name
        self.score = score

    def __lt__(self, other):
        return self.score < other.score


stu1 = Student("张三", 90)
stu2 = Student("李四", 85)

print(stu1 < stu2)
print(stu1 > stu2)

输出:

False
True

这里定义了 __lt__,Python 在某些情况下也能配合反向比较完成 >

教学时可以先讲 __eq____lt__

23. sorted() 和 lt

如果对象定义了 __lt__,就可以用 sorted() 排序。

示例:

class Student:
    def __init__(self, name, score):
        self.name = name
        self.score = score

    def __lt__(self, other):
        return self.score < other.score

    def __repr__(self):
        return f"Student({self.name!r}, {self.score})"


students = [
    Student("张三", 90),
    Student("李四", 85),
    Student("王五", 95)
]

print(sorted(students))

输出:

[Student('李四', 85), Student('张三', 90), Student('王五', 95)]

因为 __lt__ 定义了小于比较规则。

24. hash 支持哈希

如果对象想作为字典的键,或者放入集合,通常需要可哈希。

可哈希对象需要有稳定的哈希值。

特殊方法:

__hash__

示例:

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

    def __eq__(self, other):
        if not isinstance(other, Point):
            return False
        return self.x == other.x and self.y == other.y

    def __hash__(self):
        return hash((self.x, self.y))


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

points = {p1, p2}

print(len(points))

输出:

1

因为 p1p2 被认为相等,哈希值也相同。

25. hash 的注意事项

如果定义了 __eq__,但没有定义 __hash__,对象通常会变成不可哈希。

示例:

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

    def __eq__(self, other):
        return self.x == other.x and self.y == other.y

此时:

p = Point(1, 2)
s = {p}

可能会报错。

注意:

如果对象是可变的,通常不建议让它可哈希。

因为对象内容变了,哈希值也可能变,这会影响集合和字典。

26. 运算符重载是什么

运算符重载指的是:

让自定义对象支持 +、-、*、/ 等运算符。

例如:

1 + 2

会做数字加法。

"a" + "b"

会做字符串拼接。

如果我们希望自己定义的对象也能使用 +,就可以定义 __add__

27. add 支持 +

示例:两个坐标点相加。

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

    def __add__(self, other):
        return Point(self.x + other.x, self.y + other.y)

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


p1 = Point(1, 2)
p2 = Point(3, 4)

p3 = p1 + p2

print(p3)

输出:

(4, 6)

当执行:

p1 + p2

Python 会调用:

p1.__add__(p2)

28. 常见算术运算特殊方法

运算符 特殊方法
+ __add__
- __sub__
* __mul__
/ __truediv__
// __floordiv__
% __mod__
** __pow__

示例:向量乘以数字。

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

    def __mul__(self, number):
        return Vector(self.x * number, self.y * number)

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


v = Vector(2, 3)
print(v * 4)

输出:

Vector(8, 12)

29. 反向运算 radd

看这个例子:

class Number:
    def __init__(self, value):
        self.value = value

    def __add__(self, other):
        return self.value + other


n = Number(10)

print(n + 5)

输出:

15

但是:

print(5 + n)

可能会报错。

因为 5 + n 会先尝试让整数处理这个加法。

如果希望支持反向加法,可以定义 __radd__

class Number:
    def __init__(self, value):
        self.value = value

    def __add__(self, other):
        return self.value + other

    def __radd__(self, other):
        return other + self.value


n = Number(10)

print(n + 5)
print(5 + n)

输出:

15
15

常见反向运算:

运算符 正向方法 反向方法
+ __add__ __radd__
- __sub__ __rsub__
* __mul__ __rmul__
/ __truediv__ __rtruediv__

30. 原地运算 iadd

+= 对应的方法是 __iadd__

示例:

class Counter:
    def __init__(self, count):
        self.count = count

    def __iadd__(self, value):
        self.count += value
        return self

    def __str__(self):
        return str(self.count)


c = Counter(10)
c += 5

print(c)

输出:

15

注意:

__iadd__ 通常要返回 self。

如果没有定义 __iadd__,Python 可能会退而使用 __add__

31. getitem 支持下标访问

如果希望对象支持下标访问:

obj[index]

可以定义 __getitem__

示例:

class MyList:
    def __init__(self, data):
        self.data = data

    def __getitem__(self, index):
        return self.data[index]


nums = MyList([10, 20, 30])

print(nums[0])
print(nums[1])

输出:

10
20

当执行:

nums[0]

Python 会调用:

nums.__getitem__(0)

32. setitem 支持下标赋值

如果希望对象支持:

obj[index] = value

可以定义 __setitem__

示例:

class MyList:
    def __init__(self, data):
        self.data = data

    def __getitem__(self, index):
        return self.data[index]

    def __setitem__(self, index, value):
        self.data[index] = value


nums = MyList([10, 20, 30])

nums[1] = 200

print(nums[1])

输出:

200

33. delitem 支持删除元素

如果希望对象支持:

del obj[index]

可以定义 __delitem__

示例:

class MyList:
    def __init__(self, data):
        self.data = data

    def __getitem__(self, index):
        return self.data[index]

    def __delitem__(self, index):
        del self.data[index]

    def __str__(self):
        return str(self.data)


nums = MyList([10, 20, 30])

del nums[1]

print(nums)

输出:

[10, 30]

34. contains 支持 in

如果希望对象支持:

元素 in 对象

可以定义 __contains__

示例:

class Team:
    def __init__(self, members):
        self.members = members

    def __contains__(self, name):
        return name in self.members


team = Team(["张三", "李四", "王五"])

print("张三" in team)
print("赵六" in team)

输出:

True
False

当执行:

"张三" in team

Python 会调用:

team.__contains__("张三")

35. 容器类综合案例

class StudentGroup:
    def __init__(self):
        self.students = []

    def add_student(self, name):
        self.students.append(name)

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

    def __getitem__(self, index):
        return self.students[index]

    def __contains__(self, name):
        return name in self.students

    def __str__(self):
        return f"学生组:{self.students}"


group = StudentGroup()

group.add_student("张三")
group.add_student("李四")

print(len(group))
print(group[0])
print("李四" in group)
print(group)

输出:

2
张三
True
学生组:['张三', '李四']

这个对象现在像一个简单容器:

可以 len()
可以下标访问
可以用 in 判断
可以 print()

36. iter 支持 for 循环

如果希望对象可以被 for 循环遍历,可以定义 __iter__

示例:

class Team:
    def __init__(self, members):
        self.members = members

    def __iter__(self):
        return iter(self.members)


team = Team(["张三", "李四", "王五"])

for member in team:
    print(member)

输出:

张三
李四
王五

这里:

iter(self.members)

返回列表的迭代器。

37. next 支持迭代器

迭代器对象通常需要实现:

__iter__
__next__

示例:自定义一个从 1 数到 n 的计数器。

class CountUp:
    def __init__(self, max_num):
        self.max_num = max_num
        self.current = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.current >= self.max_num:
            raise StopIteration

        self.current += 1
        return self.current


counter = CountUp(3)

for num in counter:
    print(num)

输出:

1
2
3

当迭代结束时,__next__ 应该抛出:

StopIteration

这告诉 Python 循环结束。

38. 迭代器的注意事项

上面的 CountUp 对象本身就是迭代器。

它有一个特点:

遍历一次之后,current 已经到末尾。
再次遍历不会重新开始。

示例:

counter = CountUp(3)

for num in counter:
    print(num)

for num in counter:
    print(num)

第二次循环不会输出内容。

如果希望每次 for 都重新开始,通常让 __iter__ 返回一个新的迭代器。

例如:

class CountUp:
    def __init__(self, max_num):
        self.max_num = max_num

    def __iter__(self):
        return iter(range(1, self.max_num + 1))

教学时可以先讲简单版本,再补充这个注意点。

39. call 让对象像函数一样调用

如果一个类定义了 __call__,它的对象就可以像函数一样调用。

示例:

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

    def __call__(self):
        print(f"你好,{self.name}")


greet = Greeter("张三")

greet()

输出:

你好,张三

当执行:

greet()

Python 会调用:

greet.__call__()

40. call 的使用场景

__call__ 常见于:

需要保存状态的函数对象
装饰器类
回调对象
模型预测对象

简单案例:计数调用次数。

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

    def __call__(self):
        self.count += 1
        return self.count


counter = Counter()

print(counter())
print(counter())
print(counter())

输出:

1
2
3

这里 counter 像函数一样被调用,同时还能保存自己的状态。

41. enterexit 支持 with

如果希望对象支持 with 语句,需要实现:

__enter__
__exit__

示例:

class DemoContext:
    def __enter__(self):
        print("进入 with")
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        print("离开 with")


with DemoContext() as demo:
    print("执行 with 内部代码")

输出:

进入 with
执行 with 内部代码
离开 with

执行顺序:

进入 with 时调用 __enter__。
with 代码块结束时调用 __exit__。

42. 上下文管理器的用途

上下文管理器常用于:

打开和关闭文件
获取和释放资源
连接和断开数据库
加锁和释放锁
临时切换环境

例如文件操作:

with open("data.txt", "w", encoding="utf-8") as f:
    f.write("hello")

with 的好处是:

即使中间发生错误,也能尽量执行清理操作。

自定义文件风格示例:

class SimpleFile:
    def __init__(self, filename):
        self.filename = filename
        self.file = None

    def __enter__(self):
        self.file = open(self.filename, "w", encoding="utf-8")
        return self.file

    def __exit__(self, exc_type, exc_value, traceback):
        self.file.close()


with SimpleFile("demo.txt") as f:
    f.write("你好")

教学时可以强调:

with 适合管理需要开始和结束的资源。

43. exit 的参数

__exit__ 有三个常见参数:

def __exit__(self, exc_type, exc_value, traceback):
    pass

含义:

参数 含义
exc_type 异常类型
exc_value 异常对象
traceback 异常追踪信息

如果 with 内没有异常,这三个参数都是 None

示例:

class DemoContext:
    def __enter__(self):
        print("进入")
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        print("异常类型:", exc_type)
        print("异常信息:", exc_value)


with DemoContext():
    print(10 / 0)

会看到异常相关信息,然后程序仍然会抛出异常。

44. exit 返回值

如果 __exit__ 返回 True,表示异常被处理,不再向外抛出。

示例:

class IgnoreError:
    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        print("异常被处理:", exc_value)
        return True


with IgnoreError():
    print(10 / 0)

print("程序继续执行")

输出:

异常被处理: division by zero
程序继续执行

注意:

不要随便返回 True。
否则可能把重要错误隐藏掉。

45. getattr 访问不存在属性时调用

当访问一个不存在的属性时,Python 会尝试调用 __getattr__

示例:

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

    def __getattr__(self, attr_name):
        return f"属性 {attr_name} 不存在"


stu = Student("张三")

print(stu.name)
print(stu.age)

输出:

张三
属性 age 不存在

说明:

stu.name 存在,正常访问。
stu.age 不存在,触发 __getattr__。

46. getattribute 每次访问属性都会调用

__getattribute____getattr__ 更底层。

对象每次访问属性时,都会先调用 __getattribute__

示例:

class Demo:
    def __init__(self):
        self.name = "张三"

    def __getattribute__(self, attr_name):
        print("正在访问:", attr_name)
        return object.__getattribute__(self, attr_name)


demo = Demo()
print(demo.name)

输出:

正在访问: name
张三

注意:

__getattribute__ 很容易写出递归错误。
初学阶段了解即可,不建议随便重写。

错误示例:

def __getattribute__(self, attr_name):
    return self.attr_name

这会再次触发 __getattribute__,可能导致无限递归。

正确访问底层属性通常使用:

object.__getattribute__(self, attr_name)

47. setattr 设置属性时调用

当设置对象属性时,会调用 __setattr__

示例:

class Demo:
    def __setattr__(self, name, value):
        print(f"设置属性:{name} = {value}")
        object.__setattr__(self, name, value)


demo = Demo()
demo.name = "张三"
demo.age = 18

print(demo.name)
print(demo.age)

输出:

设置属性:name = 张三
设置属性:age = 18
张三
18

注意:

在 __setattr__ 中设置属性时,应该使用 object.__setattr__。
否则可能无限递归。

错误写法:

def __setattr__(self, name, value):
    self.name = value

这样会再次触发 __setattr__

48. delattr 删除属性时调用

当删除属性时,会调用 __delattr__

示例:

class Demo:
    def __delattr__(self, name):
        print(f"删除属性:{name}")
        object.__delattr__(self, name)


demo = Demo()
demo.name = "张三"

del demo.name

输出:

删除属性:name

这种方法可以控制属性删除行为。

初学阶段了解即可。

49. dict 查看对象属性字典

__dict__ 不是普通方法,而是常见的特殊属性。

它可以查看对象保存的属性。

示例:

class Student:
    def __init__(self, name, age):
        self.name = name
        self.age = age


stu = Student("张三", 18)

print(stu.__dict__)

输出:

{'name': '张三', 'age': 18}

这对理解对象属性很有帮助。

也可以查看类中的内容:

print(Student.__dict__)

输出会包含类中的方法、属性等信息。

50. slots 限制对象属性

__slots__ 可以限制对象能拥有哪些属性。

示例:

class Student:
    __slots__ = ("name", "age")

    def __init__(self, name, age):
        self.name = name
        self.age = age


stu = Student("张三", 18)

stu.name = "李四"
stu.age = 19

stu.score = 90

最后一行会报错。

因为 __slots__ 只允许:

name
age

不允许新增:

score

__slots__ 常用于:

限制动态添加属性
节省大量对象的内存

初学阶段了解即可。

51. class 查看对象所属类

对象可以通过 __class__ 查看自己属于哪个类。

示例:

class Student:
    pass


stu = Student()

print(stu.__class__)

输出类似:

<class '__main__.Student'>

也可以使用:

type(stu)

通常教学中更推荐先讲:

type(stu)

因为更直观。

52. callable() 和 call

如果对象定义了 __call__,那么 callable() 会返回 True

示例:

class A:
    def __call__(self):
        pass


class B:
    pass


a = A()
b = B()

print(callable(a))
print(callable(b))

输出:

True
False

说明:

a 可以像函数一样调用。
b 不可以。

53. dir() 查看对象支持的属性和方法

dir() 可以查看对象拥有的属性和方法。

示例:

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

    def study(self):
        print("学习")


stu = Student("张三")

print(dir(stu))

输出中会包含很多双下划线方法。

例如:

__class__
__dict__
__str__
__repr__
study
name

教学时可以告诉学生:

很多双下划线方法是 Python 对象默认就有的。
我们可以根据需要重写其中一部分。

54. 特殊方法和内置函数对应关系

使用方式 可能调用的特殊方法
str(obj) obj.__str__()
repr(obj) obj.__repr__()
len(obj) obj.__len__()
bool(obj) obj.__bool__()
obj1 == obj2 obj1.__eq__(obj2)
obj1 < obj2 obj1.__lt__(obj2)
obj1 + obj2 obj1.__add__(obj2)
obj[index] obj.__getitem__(index)
obj[index] = value obj.__setitem__(index, value)
value in obj obj.__contains__(value)
for x in obj obj.__iter__()
obj() obj.__call__()
with obj: obj.__enter__()obj.__exit__()

这张表适合课堂总结使用。

55. 综合案例:自定义购物车

需求:

购物车可以添加商品。
可以用 len() 查看商品数量。
可以用 in 判断商品是否存在。
可以用下标访问商品。
可以打印购物车。

代码:

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

    def add(self, item):
        self.items.append(item)

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

    def __contains__(self, item):
        return item in self.items

    def __getitem__(self, index):
        return self.items[index]

    def __str__(self):
        return f"购物车:{self.items}"


cart = Cart()

cart.add("苹果")
cart.add("牛奶")
cart.add("面包")

print(len(cart))
print("牛奶" in cart)
print(cart[0])
print(cart)

输出:

3
True
苹果
购物车:['苹果', '牛奶', '面包']

这个案例中用到了:

__len__
__contains__
__getitem__
__str__

56. 综合案例:自定义分数类

需求:

定义分数类 Fraction。
支持打印。
支持两个分数相加。
支持判断两个分数是否相等。

代码:

class Fraction:
    def __init__(self, numerator, denominator):
        if denominator == 0:
            raise ValueError("分母不能为 0")
        self.numerator = numerator
        self.denominator = denominator

    def __str__(self):
        return f"{self.numerator}/{self.denominator}"

    def __add__(self, other):
        new_numerator = self.numerator * other.denominator + other.numerator * self.denominator
        new_denominator = self.denominator * other.denominator
        return Fraction(new_numerator, new_denominator)

    def __eq__(self, other):
        if not isinstance(other, Fraction):
            return False
        return self.numerator * other.denominator == other.numerator * self.denominator


f1 = Fraction(1, 2)
f2 = Fraction(1, 3)
f3 = Fraction(3, 6)

print(f1)
print(f2)
print(f1 + f2)
print(f1 == f3)

输出:

1/2
1/3
5/6
True

这个案例中:

__str__ 控制打印。
__add__ 控制 +。
__eq__ 控制 ==。

57. 综合案例:班级对象

需求:

班级对象可以保存学生。
可以 len() 得到人数。
可以 for 循环遍历学生。
可以用 in 判断学生是否在班级中。

代码:

class Classroom:
    def __init__(self, name):
        self.name = name
        self.students = []

    def add_student(self, student):
        self.students.append(student)

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

    def __iter__(self):
        return iter(self.students)

    def __contains__(self, student):
        return student in self.students

    def __str__(self):
        return f"{self.name}:{len(self)} 人"


room = Classroom("Python 一班")

room.add_student("张三")
room.add_student("李四")
room.add_student("王五")

print(room)
print(len(room))
print("李四" in room)

for student in room:
    print(student)

输出:

Python 一班:3 人
3
True
张三
李四
王五

58. 综合案例:计时上下文管理器

示例:使用 with 统计一段代码执行时间。

import time


class Timer:
    def __enter__(self):
        self.start = time.time()
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.end = time.time()
        self.cost = self.end - self.start
        print(f"耗时:{self.cost:.4f} 秒")


with Timer():
    total = 0
    for i in range(1000000):
        total += i

输出类似:

耗时:0.0523 秒

这个案例中:

__enter__ 记录开始时间。
__exit__ 记录结束时间并打印耗时。

59. 常见错误 1:特殊方法名写错

错误:

def _str_(self):
    return "对象"

正确:

def __str__(self):
    return "对象"

错误:

def _init_(self):
    pass

正确:

def __init__(self):
    pass

注意:

特殊方法前后都是两个下划线。
少一个、多一个、位置不对都不行。

60. 常见错误 2:str 返回非字符串

错误:

class Demo:
    def __str__(self):
        return 123

正确:

class Demo:
    def __str__(self):
        return "123"

记忆:

__str__ 必须返回字符串。

61. 常见错误 3:len 返回非整数

错误:

def __len__(self):
    return "3"

错误:

def __len__(self):
    return -1

正确:

def __len__(self):
    return 3

__len__ 必须返回非负整数。

62. 常见错误 4:eq 不判断类型

不推荐:

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

如果 other 没有 name 属性,就会报错。

推荐:

def __eq__(self, other):
    if not isinstance(other, Student):
        return False
    return self.name == other.name

63. 常见错误 5:运算符方法返回不合理结果

例如,两个点相加,推荐返回新的点对象。

推荐:

def __add__(self, other):
    return Point(self.x + other.x, self.y + other.y)

不推荐返回完全无关的内容:

def __add__(self, other):
    return "相加成功"

虽然语法上可能可以,但使用者会困惑。

教学原则:

特殊方法的行为应该符合人们对这个语法的直觉。

64. 常见错误 6:在 setattr 中无限递归

错误:

class Demo:
    def __setattr__(self, name, value):
        self.name = value

原因:

self.name = value 又会调用 __setattr__。
这样会无限递归。

正确:

class Demo:
    def __setattr__(self, name, value):
        object.__setattr__(self, name, value)

65. 常见错误 7:在 getattribute 中无限递归

错误:

class Demo:
    def __getattribute__(self, name):
        return self.name

原因:

self.name 又会触发 __getattribute__。

正确:

class Demo:
    def __getattribute__(self, name):
        return object.__getattribute__(self, name)

初学阶段不建议重写 __getattribute__

66. 常见错误 8:滥用魔术方法

特殊方法很强大,但不要为了炫技而滥用。

不推荐:

class Student:
    def __add__(self, other):
        print("学生开始学习")

+ 通常表示加法或合并。

如果用 + 表示“学习”,会让代码难以理解。

教学建议:

只有当语义自然时,才使用对应的特殊方法。

例如:

购物车 + 商品列表
点 + 点
向量 * 数字
len(班级)
学生 in 班级

这些语义比较自然。

67. 常见错误 9:手动调用特殊方法

不推荐:

obj.__str__()
obj.__len__()
obj.__add__(other)

更推荐:

str(obj)
len(obj)
obj + other

特殊方法是给 Python 语法和内置函数调用的。

教学记忆:

少手动调双下划线方法,多用对应语法。

68. 常见错误 10:del 中写重要逻辑

不推荐把重要逻辑依赖在 __del__ 中。

原因:

__del__ 的执行时机不总是容易预测。
程序退出时对象销毁顺序也可能复杂。

如果需要稳定释放资源,更推荐:

with 语句
显式 close() 方法
try...finally

69. 注意事项 1:特殊方法名称不能随便发明

例如:

def __print__(self):
    pass

Python 不会因为你写了 __print__ 就自动在 print(obj) 时调用它。

print(obj) 主要看:

__str__
__repr__

所以要使用 Python 规定好的特殊方法名。

70. 注意事项 2:让行为符合直觉

定义特殊方法时,要让对象行为符合语法本身的含义。

例如:

p1 + p2

适合表示两个点或向量相加。

len(team)

适合表示队伍人数。

student in classroom

适合表示学生是否在班级中。

不要让特殊方法做奇怪的事情。

71. 注意事项 3:返回值类型要合理

不同特殊方法对返回值有要求。

例如:

__str__ 返回字符串
__len__ 返回非负整数
__bool__ 返回布尔值
__iter__ 返回迭代器
__next__ 没有数据时抛 StopIteration

如果返回值不符合要求,可能会报错或导致代码难懂。

72. 注意事项 4:不要一次讲太多

特殊方法数量很多。

教学时建议分阶段讲:

第一阶段:

__init__
__str__
__repr__

第二阶段:

__len__
__eq__
__lt__
__add__

第三阶段:

__getitem__
__setitem__
__contains__
__iter__

第四阶段:

__call__
__enter__
__exit__
属性访问相关方法

这样学生更容易消化。

73. 注意事项 5:先理解普通方法,再理解特殊方法

特殊方法本质上也是方法。

只是方法名特殊,并且会被 Python 自动调用。

所以教学顺序建议:

先讲类和对象
再讲普通方法
再讲 __init__
再讲 __str__
最后逐步讲其他特殊方法

学生如果还不理解 self,直接讲很多特殊方法会比较吃力。

74. 课堂练习 1:自定义 str

要求:

  1. 定义 Student 类。
  2. nameage 属性。
  3. 定义 __str__,打印对象时显示学生信息。

参考答案:

class Student:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
        return f"学生:{self.name},年龄:{self.age}"


stu = Student("张三", 18)
print(stu)

75. 课堂练习 2:自定义 len

要求:

  1. 定义 Classroom 类。
  2. 内部保存学生列表。
  3. 定义 __len__,让 len(classroom) 返回学生数量。

参考答案:

class Classroom:
    def __init__(self, students):
        self.students = students

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


room = Classroom(["张三", "李四", "王五"])

print(len(room))

76. 课堂练习 3:自定义 eq

要求:

  1. 定义 Student 类。
  2. 如果两个学生姓名和年龄都相同,就认为相等。
  3. 使用 == 比较两个对象。

参考答案:

class Student:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __eq__(self, other):
        if not isinstance(other, Student):
            return False
        return self.name == other.name and self.age == other.age


stu1 = Student("张三", 18)
stu2 = Student("张三", 18)

print(stu1 == stu2)

77. 课堂练习 4:自定义 add

要求:

  1. 定义 Point 类。
  2. xy 属性。
  3. 支持两个点相加。
  4. 打印相加后的点。

参考答案:

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

    def __add__(self, other):
        return Point(self.x + other.x, self.y + other.y)

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


p1 = Point(1, 2)
p2 = Point(3, 4)

print(p1 + p2)

78. 课堂练习 5:自定义 contains

要求:

  1. 定义 Team 类。
  2. 内部保存成员列表。
  3. 支持 "张三" in team 判断。

参考答案:

class Team:
    def __init__(self, members):
        self.members = members

    def __contains__(self, name):
        return name in self.members


team = Team(["张三", "李四"])

print("张三" in team)
print("王五" in team)

79. 课堂练习 6:自定义 getitem

要求:

  1. 定义 StudentGroup 类。
  2. 内部保存学生列表。
  3. 支持通过下标获取学生。

参考答案:

class StudentGroup:
    def __init__(self, students):
        self.students = students

    def __getitem__(self, index):
        return self.students[index]


group = StudentGroup(["张三", "李四", "王五"])

print(group[0])
print(group[1])

80. 课堂练习 7:自定义 iter

要求:

  1. 定义 StudentGroup 类。
  2. 内部保存学生列表。
  3. 支持 for 循环遍历。

参考答案:

class StudentGroup:
    def __init__(self, students):
        self.students = students

    def __iter__(self):
        return iter(self.students)


group = StudentGroup(["张三", "李四", "王五"])

for student in group:
    print(student)

81. 课堂练习 8:自定义 call

要求:

  1. 定义 Greeter 类。
  2. 对象可以像函数一样调用。
  3. 调用时打印问候语。

参考答案:

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

    def __call__(self):
        print(f"你好,{self.name}")


greet = Greeter("张三")

greet()

82. 课堂练习 9:自定义上下文管理器

要求:

  1. 定义 DemoContext 类。
  2. 进入 with 时打印 "进入"
  3. 离开 with 时打印 "离开"

参考答案:

class DemoContext:
    def __enter__(self):
        print("进入")
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        print("离开")


with DemoContext():
    print("执行代码")

83. 课堂练习 10:判断输出

阅读代码,判断输出:

class Box:
    def __init__(self, items):
        self.items = items

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

    def __bool__(self):
        return len(self.items) > 0


box1 = Box([1, 2, 3])
box2 = Box([])

print(len(box1))
print(bool(box1))
print(bool(box2))

答案:

3
True
False

84. 教学总结

特殊方法是 Python 类中非常重要的一部分。

一句话总结:

特殊方法是前后带双下划线的方法,
Python 会在特定语法或内置函数中自动调用它们。

核心知识:

  1. 特殊方法也叫魔术方法、dunder 方法。
  2. 特殊方法名称由 Python 规定,不能随便发明。
  3. __init__ 用来初始化对象。
  4. __str__ 控制 print() 的显示结果。
  5. __repr__ 更适合调试和开发者查看。
  6. __len__ 支持 len()
  7. __bool__ 支持真假判断。
  8. __eq____lt__ 等支持比较。
  9. __add__ 等支持运算符重载。
  10. __getitem____setitem____contains__ 支持容器行为。
  11. __iter____next__ 支持迭代。
  12. __call__ 让对象可以像函数一样调用。
  13. __enter____exit__ 支持 with 语句。
  14. 属性访问相关特殊方法很强大,但要谨慎使用。

常用对应关系:

print(obj)       # __str__
repr(obj)        # __repr__
len(obj)         # __len__
bool(obj)        # __bool__
obj1 == obj2     # __eq__
obj1 + obj2      # __add__
obj[index]       # __getitem__
value in obj     # __contains__
for x in obj     # __iter__
obj()            # __call__
with obj:        # __enter__ 和 __exit__

课堂记忆口诀:

双下划线前后包,
Python 自动来调用。
打印对象看 str,
调试看 repr。
长度使用 len,
相等看 eq。
加法找 add,
下标找 item。
for 循环靠 iter,
with 语句 enter exit。

最后记住:

特殊方法不是越多越好。
只有当对象需要支持某种 Python 语法时,
才去实现对应的特殊方法。
0
博主关闭了当前页面的评论