2025-08-22 Python进阶10——魔术方法
文章目录
- 1 构造/销毁对象
- 1.1 `__init__`:初始化对象(常用)
- 1.2 `__new__`:创建对象(极少直接用)
- 1.3 `__del__`:销毁对象(慎用)
- 2 字符串输出
- 2.1 `__str__`:面向用户的字符串表示
- 2.2 `__repr__`:面向开发者的字符串表示
- 3 比较运算重载
- 3.1 `__eq__`:实现等于(==)比较
- 3.2 `__ne__`:实现不等于(!=)比较
- 3.3 `__gt__`:实现大于(>)比较
- 3.4 `__ge__`:实现大于等于(>=)比较
- 3.5 `__lt__`:实现小于(<)比较
- 3.6 `__le__`:实现小于等于(<=)比较
- 4 算术运算重载
- 4.1 `__add__` 与 `__radd__`:实现加法(+)运算
- 4.2 `__sub__` 与 `__rsub__`:实现减法(-)运算
- 4.3 `__mul__` 与 `__rmul__`:实现乘法(*)运算
- 4.4 `__truediv__` 与 `__rtruediv__`:实现真除法(/)运算
- 4.5 `__floordiv__` 与 `__rfloordiv__`:实现地板除法(//)运算
- 4.6 `__mod__` 与 `__rmod__`:实现取模(%)运算
- 4.7 `__pow__` 与 `__rpow__`:实现幂运算(**)
- 5 容器行为重载
- 5.1 `__len__`:获取容器长度
- 5.2 `__getitem__`:访问容器元素
- 5.3 `__setitem__`:设置容器元素
- 5.4 `__delitem__`:删除容器元素
- 5.5 `__contains__`:判断元素是否存在
- 5.6 `__iter__`:迭代容器元素
- 6 上下文管理器
- 6.1 `__enter__`:进入上下文时准备资源
- 6.2 `__exit__`:退出上下文时清理资源
- 7 可调用对象
- 7.1 `__call__`:使对象可像函数一样调用
- 8 属性访问控制相关魔术方法
- 8.1 `__getattr__`:访问不存在的属性时
- 8.2 `__getattribute__`:访问任何属性时
- 8.3 `__setattr__`:设置属性时
- 8.4 `__delattr__`:删除属性时
- 9 描述符协议相关魔术方法
- 9.1 `__get__`:获取描述符值时
- 9.2 `__set__`:设置描述符值时
- 9.3 `__delete__`:删除描述符值时
- 10 数值类型转换相关魔术方法
- 10.1 `__int__`:转换为整数
- 10.2 `__float__`:转换为浮点数
- 10.3 `__bool__`:转换为布尔值
- 10.4 `__complex__`:转换为复数
- 11 使用建议
在 Python 中, 魔术方法(又称 “特殊方法”,以双下划线
__
开头和结尾)是一组自带的 “隐藏工具”,它们能让我们自定义类的行为(比如加减运算、打印显示、容器操作等),让代码更简洁、更符合直觉。
魔术方法不需要我们手动调用(比如不会直接写 obj.__init__()
),而是在特定场景下由 Python 自动触发(比如创建对象时自动执行 __init__
)。
1 构造/销毁对象
这类方法控制对象的创建、初始化和销毁,是最基础的魔术方法。
魔术方法 | 触发时机 | 作用 |
---|---|---|
__new__ | 创建类的实例时(比 __init__ 早) | 创建对象(分配内存) |
__init__ | 创建实例后自动执行 | 初始化对象(设置属性) |
__del__ | 对象被垃圾回收时 | 销毁对象(释放资源) |
1.1 __init__
:初始化对象(常用)
创建实例时,Python 会自动调用 __init__
给对象设置初始属性。
注意:它不是 “创建对象” 的方法,而是 “初始化已创建对象” 的方法。
class Student:# 定义 __init__,self 代表实例本身,name/age 是创建实例时需要传入的参数def __init__(self, name, age):self.name = name # 给实例绑定 name 属性self.age = age # 给实例绑定 age 属性# 创建实例时,自动触发 __init__,传入 name 和 age
stu1 = Student("小明", 18)
print(stu1.name) # 输出:小明(__init__ 已帮我们设置好属性)
print(stu1.age) # 输出:18
1.2 __new__
:创建对象(极少直接用)
__new__
是 Python 中唯一能创建对象的魔术方法,它会先分配内存,再把创建好的对象传给 __init__
初始化。
通常无需自定义,只有在 “单例模式”(一个类只能创建一个实例)等特殊场景才会用。
class Singleton:# 用一个类属性存储唯一实例_instance = None# __new__ 必须返回创建的对象,参数 cls 代表当前类def __new__(cls, *args, **kwargs):# 如果还没有实例,就创建一个if cls._instance is None:cls._instance = super().__new__(cls) # 调用父类的 __new__ 创建对象# 无论是否新建,都返回同一个实例return cls._instance# 测试:两个实例其实是同一个对象
obj1 = Singleton()
obj2 = Singleton()
print(obj1 is obj2) # 输出:True(地址相同,是同一个实例)
1.3 __del__
:销毁对象(慎用)
当对象不再被使用(没有变量引用它),Python 会自动调用 __del__
释放资源(比如关闭文件、断开数据库连接)。
注意:无法确定 __del__
何时执行(由垃圾回收机制决定),不建议依赖它做关键操作。
class FileHandler:def __init__(self, filename):self.file = open(filename, "w") # 打开文件print("文件已打开")# 销毁对象时关闭文件def __del__(self):self.file.close()print("文件已关闭(__del__ 触发)")# 创建实例(打开文件)
fh = FileHandler("test.txt")
# 手动删除引用(让对象被回收)
del fh # 输出:文件已关闭(__del__ 触发)
2 字符串输出
当我们用 print(obj)
或 str(obj)
查看对象时,Python 会自动调用这类方法,默认情况下会显示 “类名 + 内存地址”(比如 <__main__.Student object at 0x00000123456789AB>
),很不友好。自定义这类方法可以让输出更直观。
魔术方法 | 触发时机 | 作用 |
---|---|---|
__str__ | print(obj) 、str(obj) 时 | 给用户看的 “友好字符串” |
__repr__ | repr(obj) 、交互式环境直接输 obj 时 | 给开发者看的 “精确字符串”(可用于重建对象) |
2.1 __str__
:面向用户的字符串表示
class Book:def __init__(self, title, author):self.title = titleself.author = authordef __str__(self):return f"《{self.title}》(作者:{self.author})"book = Book("Python编程入门", "张三")
print(book) # 输出: 《Python编程入门》(作者:张三)
print(str(book)) # 输出: 《Python编程入门》(作者:张三)
2.2 __repr__
:面向开发者的字符串表示
__repr__
主要用于调试,返回的字符串应尽可能精确地描述对象,理想情况下可以用该字符串重建对象。
class Point:def __init__(self, x, y):self.x = xself.y = ydef __repr__(self):return f"Point(x={self.x}, y={self.y})"p = Point(3, 5)
print(repr(p)) # 输出: Point(x=3, y=5)
# 在交互式环境中直接输入 p 也会显示 __repr__ 的结果
3 比较运算重载
和算术运算类似,==
、>
、<
等比较操作也对应专门的魔术方法。比如 a == b
会触发 a.__eq__(b)
,a > b
会触发 a.__gt__(b)
。
魔术方法 | 对应运算 | 作用 |
---|---|---|
__eq__ | a == b | 等于 |
__ne__ | a != b | 不等于(默认是 not __eq__ ,一般不用重写) |
__gt__ | a > b | 大于 |
__lt__ | a < b | 小于 |
__ge__ | a >= b | 大于等于(默认是 __gt__ or __eq__ ) |
__le__ | a <= b | 小于等于(默认是 __lt__ or __eq__ ) |
3.1 __eq__
:实现等于(==)比较
__eq__
定义了对象的相等判断逻辑,当使用 ==
运算符时自动调用。
class Student:def __init__(self, student_id):self.student_id = student_id # 学号唯一标识学生def __eq__(self, other):# 两个学生学号相同则认为相等if isinstance(other, Student):return self.student_id == other.student_idreturn Falses1 = Student(1001)
s2 = Student(1001)
s3 = Student(1002)
print(s1 == s2) # 输出: True
print(s1 == s3) # 输出: False
3.2 __ne__
:实现不等于(!=)比较
__ne__
方法用于定义 “不等于” 比较的逻辑,当使用 !=
运算符时自动调用。默认情况下,它返回 not __eq__
的结果,也可以根据需要自定义实现。
class Employee:def __init__(self, id):self.id = id # 员工IDdef __ne__(self, other):"""判断两个员工的ID是否不同"""if isinstance(other, Employee):return self.id != other.idreturn True # 与非Employee类型比较视为不同# 测试
e1 = Employee(1001)
e2 = Employee(1002)
e3 = Employee(1001)
print(e1 != e2) # 输出: True(ID不同)
print(e1 != e3) # 输出: False(ID相同)
print(e1 != "employee") # 输出: True(与非Employee类型比较)
3.3 __gt__
:实现大于(>)比较
__gt__
定义了对象的大于判断逻辑,当使用 >
运算符时自动调用。
class Score:def __init__(self, value):self.value = valuedef __gt__(self, other):# 分数值大的认为更大return self.value > other.values1 = Score(95)
s2 = Score(88)
print(s1 > s2) # 输出: True
3.4 __ge__
:实现大于等于(>=)比较
__ge__
方法用于定义 “大于等于” 比较的逻辑,当使用 >=
运算符时自动调用,返回布尔值表示比较结果。
class Weight:def __init__(self, kg):self.kg = kg # 重量(千克)def __ge__(self, other):"""判断当前重量是否大于等于另一个重量"""if isinstance(other, Weight):return self.kg >= other.kgraise TypeError("只能与Weight类型进行比较")# 测试
w1 = Weight(10)
w2 = Weight(8)
w3 = Weight(10)
print(w1 >= w2) # 输出: True(10kg >= 8kg)
print(w1 >= w3) # 输出: True(10kg >= 10kg)
print(w2 >= w1) # 输出: False(8kg >= 10kg 不成立)
3.5 __lt__
:实现小于(<)比较
__lt__
方法用于定义 “小于” 比较的逻辑,当使用 <
运算符比较两个对象时自动调用。该方法应返回布尔值,表示当前对象是否小于另一个对象。
class Product:def __init__(self, price):self.price = price # 产品价格def __lt__(self, other):"""判断当前产品价格是否小于另一个产品价格"""if isinstance(other, Product):return self.price < other.price# 处理与非Product类型比较的情况raise TypeError("只能与Product类型进行比较")# 测试
p1 = Product(99.9)
p2 = Product(149.9)
print(p1 < p2) # 输出: True(99.9 < 149.9)
print(p2 < p1) # 输出: False(149.9 < 99.9 不成立)
3.6 __le__
:实现小于等于(<=)比较
__le__
方法用于定义 “小于等于” 比较的逻辑,当使用 <=
运算符时自动调用,返回布尔值表示比较结果。
class Student:def __init__(self, score):self.score = score # 学生分数def __le__(self, other):"""判断当前学生分数是否小于等于另一个学生分数"""if isinstance(other, Student):return self.score <= other.scoreraise TypeError("只能与Student类型进行比较")# 测试
s1 = Student(85)
s2 = Student(90)
s3 = Student(85)
print(s1 <= s2) # 输出: True(85 <= 90)
print(s1 <= s3) # 输出: True(85 <= 85)
print(s2 <= s1) # 输出: False(90 <= 85 不成立)
4 算术运算重载
我们可以让自定义类的实例支持 +
、-
、*
、/
等运算,只需实现对应的魔术方法。比如 a + b
会自动触发 a.__add__(b)
。
除了基本运算方法外,还有对应的反向方法,用于处理运算顺序相反的情况(如 b + a
当 a
不支持加法时,会调用 b
的反向方法)。
魔术方法 | 对应运算 | 作用 | 反向方法 |
---|---|---|---|
__add__ | a + b | 加法 | __radd__ |
__sub__ | a - b | 减法 | __rsub__ |
__mul__ | a * b | 乘法 | __rmul__ |
__truediv__ | a / b | 真除法(返回浮点数) | __rtruediv__ |
__floordiv__ | a // b | 地板除法(返回整数) | __rfloordiv__ |
__mod__ | a % b | 取模 | __rmod__ |
__pow__ | a ** b | 乘方 | __rpow__ |
4.1 __add__
与 __radd__
:实现加法(+)运算
__add__
方法用于定义 “加法” 运算的逻辑,当使用 +
运算符时自动调用。__radd__
作为反向方法,当左侧对象不支持加法运算时,会调用右侧对象的 __radd__
方法。
class Number:def __init__(self, value):self.value = valuedef __add__(self, other):"""定义当前对象 + 另一个对象的逻辑"""if isinstance(other, Number):return Number(self.value + other.value)elif isinstance(other, (int, float)):return Number(self.value + other)return NotImplemented # 不支持的类型返回NotImplementeddef __radd__(self, other):"""定义另一个对象 + 当前对象的逻辑(反向加法)"""# 对于加法,a + b 与 b + a 逻辑相同,直接调用 __add__return self.__add__(other)def __str__(self):return str(self.value)# 测试
n1 = Number(5)
n2 = Number(3)
print(n1 + n2) # 输出: 8(调用n1.__add__(n2))
print(n1 + 2) # 输出: 7(调用n1.__add__(2))
print(2 + n1) # 输出: 7(2不支持加法,调用n1.__radd__(2))
4.2 __sub__
与 __rsub__
:实现减法(-)运算
__sub__
方法用于定义 “减法” 运算的逻辑,当使用 -
运算符时自动调用。__rsub__
作为反向方法,处理 b - a
当 b
不支持减法时的情况。
class Length:def __init__(self, cm):self.cm = cm # 长度(厘米)def __sub__(self, other):"""定义当前对象 - 另一个对象的逻辑"""if isinstance(other, Length):return Length(self.cm - other.cm)elif isinstance(other, (int, float)):return Length(self.cm - other)return NotImplementeddef __rsub__(self, other):"""定义另一个对象 - 当前对象的逻辑(反向减法)"""if isinstance(other, (int, float)):return Length(other - self.cm)return NotImplementeddef __str__(self):return f"{self.cm}cm"# 测试
l1 = Length(10)
l2 = Length(4)
print(l1 - l2) # 输出: 6cm(调用l1.__sub__(l2))
print(l1 - 3) # 输出: 7cm(调用l1.__sub__(3))
print(15 - l1) # 输出: 5cm(调用l1.__rsub__(15))
4.3 __mul__
与 __rmul__
:实现乘法(*)运算
__mul__
方法用于定义 “乘法” 运算的逻辑,当使用 *
运算符时自动调用。__rmul__
作为反向方法,处理 b * a
当 b
不支持乘法时的情况。
class Vector2D:def __init__(self, x, y):self.x = xself.y = ydef __mul__(self, scalar):"""定义向量 * 标量的逻辑(向量缩放)"""if isinstance(scalar, (int, float)):return Vector2D(self.x * scalar, self.y * scalar)return NotImplementeddef __rmul__(self, scalar):"""定义标量 * 向量的逻辑(反向乘法)"""# 对于标量乘法,a * v 与 v * a 逻辑相同return self.__mul__(scalar)def __str__(self):return f"Vector2D({self.x}, {self.y})"# 测试
v = Vector2D(2, 3)
print(v * 2) # 输出: Vector2D(4, 6)(调用v.__mul__(2))
print(3 * v) # 输出: Vector2D(6, 9)(调用v.__rmul__(3))
4.4 __truediv__
与 __rtruediv__
:实现真除法(/)运算
__truediv__
方法用于定义 “真除法” 运算的逻辑,当使用 /
运算符时自动调用,返回浮点数结果。__rtruediv__
处理反向除法情况。
class Quantity:def __init__(self, amount):self.amount = amountdef __truediv__(self, other):"""定义当前对象 / 另一个对象的逻辑"""if isinstance(other, Quantity):return Quantity(self.amount / other.amount)elif isinstance(other, (int, float)):return Quantity(self.amount / other)return NotImplementeddef __rtruediv__(self, other):"""定义另一个对象 / 当前对象的逻辑(反向除法)"""if isinstance(other, (int, float)):return Quantity(other / self.amount)return NotImplementeddef __str__(self):return str(self.amount)# 测试
q1 = Quantity(10)
q2 = Quantity(2)
print(q1 / q2) # 输出: 5.0(调用q1.__truediv__(q2))
print(q1 / 5) # 输出: 2.0(调用q1.__truediv__(5))
print(20 / q1) # 输出: 2.0(调用q1.__rtruediv__(20))
4.5 __floordiv__
与 __rfloordiv__
:实现地板除法(//)运算
__floordiv__
方法用于定义 “地板除法” 运算的逻辑,当使用 //
运算符时自动调用,返回整数结果(向下取整)。__rfloordiv__
处理反向地板除法情况。
class Item:def __init__(self, count):self.count = count # 物品数量def __floordiv__(self, other):"""定义当前对象 // 另一个对象的逻辑(整除)"""if isinstance(other, Item):return Item(self.count // other.count)elif isinstance(other, int):return Item(self.count // other)return NotImplementeddef __rfloordiv__(self, other):"""定义另一个对象 // 当前对象的逻辑(反向地板除法)"""if isinstance(other, int):return Item(other // self.count)return NotImplementeddef __str__(self):return str(self.count)# 测试
i1 = Item(10)
i2 = Item(3)
print(i1 // i2) # 输出: 3(调用i1.__floordiv__(i2))
print(i1 // 4) # 输出: 2(调用i1.__floordiv__(4))
print(15 // i1) # 输出: 1(调用i1.__rfloordiv__(15))
4.6 __mod__
与 __rmod__
:实现取模(%)运算
__mod__
方法用于定义 “取模” 运算的逻辑,当使用 %
运算符时自动调用,返回除法的余数。__rmod__
处理反向取模情况。
class Clock:def __init__(self, hour):self.hour = hour % 24 # 确保小时在0-23之间def __mod__(self, other):"""定义当前时间 % 另一个值的逻辑(取模)"""if isinstance(other, int):return Clock(self.hour % other)return NotImplementeddef __rmod__(self, other):"""定义另一个值 % 当前时间的逻辑(反向取模)"""if isinstance(other, int):return other % self.hourreturn NotImplementeddef __str__(self):return f"{self.hour}时"# 测试
c = Clock(25) # 初始化时自动取模24,实际为1时
print(c % 12) # 输出: 1时(调用c.__mod__(12))
print(30 % c) # 输出: 30 % 1 = 0(调用c.__rmod__(30))
4.7 __pow__
与 __rpow__
:实现幂运算(**)
__pow__
方法用于定义 “幂运算” 的逻辑,当使用 **
运算符时自动调用,如 a** b
表示 a
的 b
次方。__rpow__
处理反向幂运算情况。
class Power:def __init__(self, base):self.base = basedef __pow__(self, exponent):"""定义当前对象 **指数的逻辑(幂运算)"""if isinstance(exponent, (int, float, Power)):# 如果指数是Power对象,取其base值exp_val = exponent.base if isinstance(exponent, Power) else exponentreturn Power(self.base **exp_val)return NotImplementeddef __rpow__(self, base):"""定义基数** 当前对象的逻辑(反向幂运算)"""if isinstance(base, (int, float)):return Power(base **self.base)return NotImplementeddef __str__(self):return str(self.base)# 测试
p = Power(2)
print(p** 3) # 输出: 8(调用p.__pow__(3),2^3=8)
print(3 **p) # 输出: 9(调用p.__rpow__(3),3^2=9)p2 = Power(3)
print(p** p2) # 输出: 8(调用p.__pow__(p2),2^3=8)
5 容器行为重载
Python 中的列表([]
)、字典({}
)等容器,支持 len()
(求长度)、obj[key]
(索引 / 键访问)、for in
(迭代)等操作。我们可以让自定义类也支持这些行为,只需实现对应的魔术方法。
魔术方法 | 触发时机 | 主要作用 |
---|---|---|
__len__ | len(obj) 时 | 返回对象长度 |
__getitem__ | obj[key] 时 | 获取指定键 / 索引的元素 |
__setitem__ | obj[key] = value 时 | 设置指定键 / 索引的元素 |
__delitem__ | del obj[key] 时 | 移除指定位置的元素 |
__contains__ | item in obj 运算符时 | 判断元素是否存在 |
__iter__ | for item in obj 循环时 | 返回迭代器 |
5.1 __len__
:获取容器长度
__len__
方法用于定义容器的长度计算逻辑,当调用 len()
函数时自动触发,返回一个整数表示容器中元素的数量。
class ShoppingCart:def __init__(self):self.items = [] # 存储购物车中的商品def add_item(self, item):"""添加商品到购物车"""self.items.append(item)def __len__(self):"""返回购物车中商品的数量"""return len(self.items)# 测试
cart = ShoppingCart()
cart.add_item("苹果")
cart.add_item("香蕉")
cart.add_item("橙子")print(len(cart)) # 输出: 3(购物车中有3件商品)
5.2 __getitem__
:访问容器元素
__getitem__
方法用于定义通过键或索引访问容器元素的逻辑,当使用 obj[key]
语法时自动触发,返回指定位置的元素。支持整数索引、切片和键访问。
class CustomList:def __init__(self, data):self.data = data # 存储列表数据def __getitem__(self, key):"""支持索引和切片访问元素"""# 对索引进行范围检查if isinstance(key, int):if key < 0:key = len(self.data) + keyif 0 <= key < len(self.data):return self.data[key]raise IndexError("索引超出范围")# 支持切片操作elif isinstance(key, slice):return self.data[key]else:raise TypeError("索引必须是整数或切片")# 测试
cl = CustomList(["a", "b", "c", "d", "e"])
print(cl[0]) # 输出: a(访问单个元素)
print(cl[-1]) # 输出: e(访问最后一个元素)
print(cl[1:4]) # 输出: ['b', 'c', 'd'](切片访问)
5.3 __setitem__
:设置容器元素
__setitem__
方法用于定义为容器指定位置设置元素的逻辑,当使用 obj[key] = value
语法时自动触发,将值存储到指定位置。
class MutableArray:def __init__(self, size, default=0):self.size = sizeself.data = [default] * size # 初始化指定大小的数组def __setitem__(self, index, value):"""设置指定索引位置的元素值"""if isinstance(index, int):# 处理负索引if index < 0:index = self.size + indexif 0 <= index < self.size:self.data[index] = valuereturnraise IndexError("索引超出数组范围")raise TypeError("索引必须是整数")def __getitem__(self, index):"""获取指定索引位置的元素值"""if isinstance(index, int):if index < 0:index = self.size + indexif 0 <= index < self.size:return self.data[index]raise IndexError("索引超出数组范围")raise TypeError("索引必须是整数")# 测试
arr = MutableArray(5) # 创建大小为5的数组,默认值为0
arr[0] = 10
arr[2] = 20
arr[-1] = 30print(arr[0]) # 输出: 10
print(arr[2]) # 输出: 20
print(arr[-1]) # 输出: 30
5.4 __delitem__
:删除容器元素
__delitem__
方法用于定义删除容器中指定位置元素的逻辑,当使用 del obj[key]
语法时自动触发,移除指定位置的元素。
class TodoList:def __init__(self):self.tasks = [] # 存储待办任务def add_task(self, task):"""添加任务"""self.tasks.append(task)def __delitem__(self, index):"""删除指定索引的任务"""if isinstance(index, int):if 0 <= index < len(self.tasks) or -len(self.tasks) <= index < 0:del self.tasks[index]returnraise IndexError("任务索引不存在")raise TypeError("索引必须是整数")def __str__(self):return str(self.tasks)# 测试
todo = TodoList()
todo.add_task("学习Python")
todo.add_task("锻炼身体")
todo.add_task("阅读书籍")print(todo) # 输出: ['学习Python', '锻炼身体', '阅读书籍']del todo[1] # 删除索引为1的任务
print(todo) # 输出: ['学习Python', '阅读书籍']
5.5 __contains__
:判断元素是否存在
__contains__
方法用于定义判断元素是否存在于容器中的逻辑,当使用 item in obj
或 item not in obj
语法时自动触发,返回布尔值表示判断结果。
class BookCollection:def __init__(self):self.books = set() # 用集合存储书籍,提高查找效率def add_book(self, book_title):"""添加书籍到集合"""self.books.add(book_title)def __contains__(self, book_title):"""判断书籍是否在集合中"""return book_title in self.books# 测试
collection = BookCollection()
collection.add_book("Python编程入门")
collection.add_book("数据结构与算法")print("Python编程入门" in collection) # 输出: True
print("机器学习实战" in collection) # 输出: False
print("数据结构与算法" not in collection) # 输出: False
5.6 __iter__
:迭代容器元素
__iter__
方法用于定义容器的迭代逻辑,当使用 for
循环遍历容器时自动触发,返回一个迭代器对象(通常是容器自身或内置迭代器),配合 __next__
方法实现元素遍历。
class NumberRange:def __init__(self, start, end, step=1):self.start = startself.end = endself.step = stepdef __iter__(self):"""返回迭代器,用于遍历范围内的数字"""self.current = self.startreturn selfdef __next__(self):"""定义每次迭代返回的下一个值"""if (self.step > 0 and self.current < self.end) or \(self.step < 0 and self.current > self.end):value = self.currentself.current += self.stepreturn value# 迭代结束时抛出StopIteration异常raise StopIteration# 测试
# 遍历1到10的偶数
for num in NumberRange(2, 11, 2):print(num, end=" ") # 输出: 2 4 6 8 10print()# 遍历10到1的奇数
for num in NumberRange(9, 0, -2):print(num, end=" ") # 输出: 9 7 5 3 1
6 上下文管理器
上下文管理器用于定义对象在 with
语句中的行为,通过实现 __enter__
和 __exit__
方法,可以实现资源的自动分配与释放,确保资源在使用后得到正确处理(如文件关闭、数据库连接断开等)。
魔术方法 | 调用时机 | 主要作用 |
---|---|---|
__enter__ | 进入 with 代码块时 | 准备资源(如打开文件、建立连接),返回的对象将赋值给 as 后的变量 |
__exit__ | 退出 with 代码块时(无论正常退出还是异常退出) | 清理资源(如关闭文件、断开连接),处理可能的异常 |
6.1 __enter__
:进入上下文时准备资源
__enter__
方法在进入 with
代码块时被调用,主要用于准备所需资源(如打开文件、建立数据库连接等)。该方法的返回值会被赋值给 with
语句中 as
后面的变量,供 with
块内部使用。
class DatabaseConnection:def __init__(self, db_name):self.db_name = db_nameself.connection = None # 数据库连接对象def __enter__(self):"""建立数据库连接并返回连接对象"""print(f"连接到数据库: {self.db_name}")# 模拟建立数据库连接self.connection = f"Connection to {self.db_name}"return self.connection # 返回连接对象给as后的变量# 后面会实现__exit__方法
6.2 __exit__
:退出上下文时清理资源
__exit__
方法在退出 with
代码块时被调用(无论代码块正常执行完毕还是因异常中断),主要用于清理资源(如关闭文件、断开连接等)。它接收三个参数,用于处理可能发生的异常:
exc_type
:异常类型(若没有异常则为None
)exc_val
:异常实例(若没有异常则为None
)exc_tb
:异常追踪信息(若没有异常则为None
)
class DatabaseConnection:def __init__(self, db_name):self.db_name = db_nameself.connection = Nonedef __enter__(self):print(f"连接到数据库: {self.db_name}")self.connection = f"Connection to {self.db_name}"return self.connectiondef __exit__(self, exc_type, exc_val, exc_tb):"""关闭数据库连接,处理可能的异常"""print(f"关闭数据库连接: {self.db_name}")self.connection = None # 模拟关闭连接# 处理异常(如果有的话)if exc_type:print(f"发生异常: {exc_type.__name__} - {exc_val}")# 返回True表示异常已处理,不会向外传播# 返回False表示异常未处理,会继续向外传播return True # 此处选择处理异常# 测试正常情况
with DatabaseConnection("mydb") as conn:print(f"使用连接: {conn} 执行操作")
# 输出:
# 连接到数据库: mydb
# 使用连接: Connection to mydb 执行操作
# 关闭数据库连接: mydb# 测试异常情况
with DatabaseConnection("mydb") as conn:print("执行操作中...")raise ValueError("数据格式错误") # 主动抛出异常
# 输出:
# 连接到数据库: mydb
# 执行操作中...
# 关闭数据库连接: mydb
# 发生异常: ValueError - 数据格式错误
7 可调用对象
可调用对象是指可以像函数一样被调用的对象。在 Python 中,通过实现 __call__
魔术方法,我们可以让自定义类的实例具备可调用性,使对象能够以 obj()
的形式被调用,就像调用函数一样。
魔术方法 | 调用时机 | 主要作用 |
---|---|---|
__call__ | 以 obj() 形式调用对象时 | 定义对象被调用时的行为,可以接收参数并返回结果 |
7.1 __call__
:使对象可像函数一样调用
__call__
方法用于定义对象被调用时的逻辑,当以 obj()
形式调用对象时自动触发。它可以接收任意数量的参数(包括位置参数和关键字参数),并返回处理结果,使对象具备类似函数的功能。
class Calculator:def __init__(self, operator):self.operator = operator # 存储运算符(+、-、*、/)def __call__(self, a, b):"""定义对象被调用时的计算逻辑"""if self.operator == "+":return a + belif self.operator == "-":return a - belif self.operator == "*":return a * belif self.operator == "/":if b == 0:raise ValueError("除数不能为零")return a / belse:raise ValueError(f"不支持的运算符: {self.operator}")# 创建可调用对象(加法计算器)
add = Calculator("+")
print(add(3, 5)) # 输出: 8(对象像函数一样被调用)# 创建减法计算器
subtract = Calculator("-")
print(subtract(10, 4)) # 输出: 6# 创建乘法计算器
multiply = Calculator("*")
print(multiply(7, 6)) # 输出: 42
带状态的可调用对象
__call__
方法的一个重要特性是可以访问对象的状态(属性),因此可调用对象可以在多次调用之间保持状态,这是普通函数难以做到的。
class Counter:def __init__(self, start=0):self.count = start # 初始化计数器起始值def __call__(self, step=1):"""每次调用计数器,增加指定步长并返回当前值"""self.count += stepreturn self.count# 创建计数器对象,从0开始
counter = Counter()print(counter()) # 输出: 1(默认步长为1)
print(counter(2)) # 输出: 3(步长为2,1+2=3)
print(counter(5)) # 输出: 8(步长为5,3+5=8)
print(counter.count) # 输出: 8(直接访问当前状态)
带关键字参数的可调用对象
__call__
方法支持关键字参数,使调用更加灵活:
class TextProcessor:def __init__(self, default_case="lower"):self.default_case = default_case # 默认大小写处理方式def __call__(self, text, case=None):"""处理文本大小写,可通过参数覆盖默认设置"""# 如果调用时未指定case,使用默认设置case = case or self.default_caseif case == "lower":return text.lower()elif case == "upper":return text.upper()elif case == "title":return text.title()else:return text# 创建文本处理器,默认转为小写
processor = TextProcessor()print(processor("Hello World")) # 输出: hello world(默认转为小写)
print(processor("hello world", case="upper")) # 输出: HELLO WORLD(转为大写)
print(processor("hello world", case="title")) # 输出: Hello World(首字母大写)
8 属性访问控制相关魔术方法
属性访问控制允许自定义类对属性的访问、设置、删除等操作进行精确控制,通过实现对应的魔术方法,可以在属性操作时添加验证、日志、计算等逻辑,增强类的安全性和灵活性。
魔术方法 | 调用时机 | 主要作用 |
---|---|---|
__getattr__ | 访问不存在的属性时 | 定义访问不存在属性的处理逻辑 |
__getattribute__ | 访问任何属性时(包括存在的) | 定义所有属性访问的统一处理逻辑 |
__setattr__ | 设置属性时(包括新增和修改) | 定义设置属性的验证或处理逻辑 |
__delattr__ | 删除属性时 | 定义删除属性的限制或处理逻辑 |
8.1 __getattr__
:访问不存在的属性时
__getattr__
方法在访问对象不存在的属性时被调用,接收属性名作为参数,可以返回默认值、计算值或抛出异常,避免因访问不存在的属性而直接报错。
class User:def __init__(self, name, age):self.name = nameself.age = agedef __getattr__(self, attr):"""处理访问不存在属性的情况"""# 为常见的不存在属性提供默认值或提示if attr == "email":return f"{self.name.lower()}@example.com" # 生成默认邮箱elif attr == "address":return "未填写地址" # 返回默认值else:# 对于其他不存在的属性,抛出 AttributeErrorraise AttributeError(f"User 对象没有属性 '{attr}'")# 测试
user = User("Alice", 30)
print(user.name) # 输出: Alice(访问存在的属性,不触发 __getattr__)
print(user.email) # 输出: alice@example.com(访问不存在的属性,触发 __getattr__)
print(user.address) # 输出: 未填写地址(访问不存在的属性,触发 __getattr__)# 访问完全不存在的属性
try:print(user.phone)
except AttributeError as e:print(e) # 输出: User 对象没有属性 'phone'
8.2 __getattribute__
:访问任何属性时
__getattribute__
方法在访问任何属性(包括存在的和不存在的)时都会被调用,优先级高于 __getattr__
。可以用于对所有属性访问进行统一控制(如日志记录、权限验证等),但需谨慎使用,避免递归调用。
class SecureData:def __init__(self, data):self.data = dataself.secret = "敏感信息"def __getattribute__(self, attr):"""控制所有属性的访问逻辑"""# 记录访问日志print(f"访问属性: {attr}")# 对敏感属性进行访问控制if attr == "secret":# 模拟权限检查has_permission = False # 假设没有权限if not has_permission:raise PermissionError("没有访问敏感信息的权限")# 获取属性值(注意:必须使用 super().__getattribute__ 避免递归)return super().__getattribute__(attr)# 测试
data = SecureData({"name": "测试数据"})
print(data.data) # 输出: 访问属性: data → {'name': '测试数据'}# 尝试访问敏感属性
try:print(data.secret)
except PermissionError as e:print(e) # 输出: 没有访问敏感信息的权限
注意:在 __getattribute__
中访问当前对象的属性时,必须使用 super().__getattribute__(attr)
或 object.__getattribute__(self, attr)
,直接使用 self.attr
会导致无限递归。
8.3 __setattr__
:设置属性时
__setattr__
方法在设置任何属性(包括新增和修改)时被调用,接收属性名和属性值作为参数,可以对属性值进行验证、转换或限制,确保属性值符合预期。
class Product:def __init__(self, name, price):self.name = name # 触发 __setattr__self.price = price # 触发 __setattr__def __setattr__(self, attr, value):"""控制属性设置的逻辑"""# 对价格进行验证if attr == "price":if not isinstance(value, (int, float)):raise TypeError("价格必须是数字类型")if value < 0:raise ValueError("价格不能为负数")# 验证通过,设置属性(注意避免递归)super().__setattr__(attr, value)# 对名称进行验证elif attr == "name":if not isinstance(value, str) or len(value.strip()) == 0:raise ValueError("商品名称必须是非空字符串")super().__setattr__(attr, value.strip())# 允许设置其他属性else:super().__setattr__(attr, value)# 测试
try:# 价格为负数(无效)p1 = Product("手机", -1000)
except ValueError as e:print(e) # 输出: 价格不能为负数try:# 名称为空(无效)p2 = Product("", 2000)
except ValueError as e:print(e) # 输出: 商品名称必须是非空字符串# 有效属性设置
p3 = Product(" 笔记本电脑 ", 5999)
print(p3.name) # 输出: 笔记本电脑(自动去除了首尾空格)
print(p3.price) # 输出: 5999
注意:在 __setattr__
中设置属性时,必须使用 super().__setattr__(attr, value)
,直接使用 self.attr = value
会导致无限递归。
8.4 __delattr__
:删除属性时
__delattr__
方法在删除属性时被调用,接收属性名作为参数,可以限制某些关键属性的删除,确保对象的完整性。
class Person:def __init__(self, name, id_card):self.name = nameself.id_card = id_card # 身份证号为关键属性,不允许删除def __delattr__(self, attr):"""控制属性删除的逻辑"""# 禁止删除关键属性if attr == "id_card":raise AttributeError("身份证号是关键属性,不允许删除")# 允许删除其他属性(注意避免递归)super().__delattr__(attr)# 测试
person = Person("张三", "110101199001011234")# 删除普通属性(允许)
del person.name
try:print(person.name)
except AttributeError:print("name 属性已被删除")# 尝试删除关键属性(禁止)
try:del person.id_card
except AttributeError as e:print(e) # 输出: 身份证号是关键属性,不允许删除
9 描述符协议相关魔术方法
描述符协议是 Python 中实现属性访问控制的高级机制,通过定义 __get__
、__set__
和 __delete__
方法,可以创建一个描述符对象,用于管理另一个类的属性。描述符可以实现复杂的属性逻辑,如类型检查、值验证、懒加载等,广泛应用于 ORM 框架、数据验证等场景。
魔术方法 | 调用时机 | 主要作用 |
---|---|---|
__get__ | 访问描述符管理的属性时 | 返回属性值,可添加获取逻辑 |
__set__ | 设置描述符管理的属性时 | 处理属性赋值,可添加验证逻辑 |
__delete__ | 删除描述符管理的属性时 | 处理属性删除,可添加限制逻辑 |
9.1 __get__
:获取描述符值时
__get__
方法在访问描述符管理的属性时被调用,接收三个参数:self
(描述符实例)、instance
(被管理的类实例)和 owner
(被管理的类)。该方法返回属性的当前值,可在返回前添加计算、转换等逻辑。
class Temperature:"""温度描述符,将摄氏度转换为华氏度返回"""def __init__(self, celsius=0):self.celsius = celsius # 存储摄氏度def __get__(self, instance, owner):"""获取值时,返回华氏度(摄氏度 * 1.8 + 32)"""if instance is None:return self # 类访问时返回描述符自身return self.celsius * 1.8 + 32class Weather:# 使用描述符管理temperature属性temperature = Temperature()# 测试
weather = Weather()
print(weather.temperature) # 输出: 32.0(默认0摄氏度 → 32华氏度)# 类访问时返回描述符自身
print(Weather.temperature) # 输出: <__main__.Temperature object at 0x...>
9.2 __set__
:设置描述符值时
__set__
方法在设置描述符管理的属性时被调用,接收三个参数:self
(描述符实例)、instance
(被管理的类实例)和 value
(要设置的值)。该方法可对值进行验证、转换后再存储,确保属性值符合预期。
class PositiveNumber:"""正数描述符,确保属性值为正数"""def __init__(self, name):self.name = name # 存储属性名,用于在实例字典中保存值def __get__(self, instance, owner):"""获取值时,从实例字典中读取"""return instance.__dict__[self.name]def __set__(self, instance, value):"""设置值时,验证是否为正数"""if not isinstance(value, (int, float)):raise TypeError("值必须是数字类型")if value <= 0:raise ValueError("值必须是正数")# 验证通过,存储到实例字典中instance.__dict__[self.name] = valueclass Product:# 使用描述符管理价格和库存属性price = PositiveNumber("price")stock = PositiveNumber("stock")def __init__(self, name, price, stock):self.name = nameself.price = price # 触发 __set__ 验证self.stock = stock # 触发 __set__ 验证# 测试
try:# 价格为负数(无效)p1 = Product("手机", -1000, 50)
except ValueError as e:print(e) # 输出: 值必须是正数try:# 库存为零(无效)p2 = Product("电脑", 5000, 0)
except ValueError as e:print(e) # 输出: 值必须是正数# 有效设置
p3 = Product("耳机", 299, 100)
print(p3.price) # 输出: 299
print(p3.stock) # 输出: 100
9.3 __delete__
:删除描述符值时
__delete__
方法在删除描述符管理的属性时被调用,接收两个参数:self
(描述符实例)和 instance
(被管理的类实例)。该方法可限制某些重要属性的删除,或在删除时执行清理操作。
class ProtectedAttribute:"""受保护的属性描述符,限制删除操作"""def __init__(self, name, value):self.name = nameself.value = valuedef __get__(self, instance, owner):return self.valuedef __set__(self, instance, value):self.value = valuedef __delete__(self, instance):"""限制删除操作,关键属性不允许删除"""if self.name in ["id", "ssn"]: # 身份证号、社会安全号等关键属性raise PermissionError(f"属性 '{self.name}' 是关键属性,不允许删除")# 允许删除非关键属性self.value = Noneclass User:# 使用描述符管理关键属性id = ProtectedAttribute("id", "1001")ssn = ProtectedAttribute("ssn", "123-45-6789")address = ProtectedAttribute("address", "未知地址")# 测试
user = User()# 尝试删除关键属性
try:del user.id
except PermissionError as e:print(e) # 输出: 属性 'id' 是关键属性,不允许删除# 尝试删除非关键属性(允许)
del user.address
print(user.address) # 输出: None(已被清空)
10 数值类型转换相关魔术方法
数值类型转换魔术方法允许自定义类的实例支持内置数值类型(如 int
、float
、bool
、complex
)的转换,通过实现这些方法,可以定义对象转换为特定数值类型时的行为,使自定义对象能更自然地参与数值运算和判断。
魔术方法 | 对应转换函数 | 调用时机 | 主要作用 |
---|---|---|---|
__int__ | int(obj) | 调用 int() 转换对象时 | 定义对象转换为整数的逻辑 |
__float__ | float(obj) | 调用 float() 转换对象时 | 定义对象转换为浮点数的逻辑 |
__bool__ | bool(obj) | 调用 bool() 转换对象或判断真假时 | 定义对象的布尔值判断逻辑 |
__complex__ | complex(obj) | 调用 complex() 转换对象时 | 定义对象转换为复数的逻辑 |
10.1 __int__
:转换为整数
__int__
方法在调用 int(obj)
时被触发,返回一个整数,表示将对象转换为整数的结果。常用于定义对象的整数表示形式。
class Distance:"""距离类,存储米为单位的距离"""def __init__(self, meters):self.meters = metersdef __int__(self):"""转换为整数时,返回米的整数部分"""return int(self.meters)# 测试
d1 = Distance(150.8)
print(int(d1)) # 输出: 150(调用 __int__ 方法)d2 = Distance(200)
print(int(d2)) # 输出: 200
10.2 __float__
:转换为浮点数
__float__
方法在调用 float(obj)
时被触发,返回一个浮点数,表示将对象转换为浮点数的结果。常用于需要精确数值表示的场景。
class Weight:"""重量类,存储千克为单位的重量"""def __init__(self, kg):self.kg = kgdef __float__(self):"""转换为浮点数时,返回千克的浮点值"""return float(self.kg)# 测试
w1 = Weight(75)
print(float(w1)) # 输出: 75.0(调用 __float__ 方法)w2 = Weight(62.5)
print(float(w2)) # 输出: 62.5
10.3 __bool__
:转换为布尔值
__bool__
方法在调用 bool(obj)
或需要判断对象真假时(如 if
语句、逻辑运算)被触发,返回 True
或 False
。如果未定义 __bool__
,Python 会使用 __len__
方法的结果(非零为 True
),如果两者都未定义,所有对象默认视为 True
。
class Inventory:"""库存类,存储商品数量"""def __init__(self, count):self.count = count # 商品数量def __bool__(self):"""库存大于0则为True,否则为False"""return self.count > 0# 测试
inv1 = Inventory(10)
print(bool(inv1)) # 输出: True(库存大于0)inv2 = Inventory(0)
print(bool(inv2)) # 输出: False(库存为0)# 在条件判断中自动触发
if inv1:print("库存充足") # 执行此句
else:print("库存不足")
10.4 __complex__
:转换为复数
__complex__
方法在调用 complex(obj)
时被触发,返回一个复数(形式为 real + imag*j
),表示将对象转换为复数的结果。常用于科学计算场景。
class Vector:"""二维向量类,存储x和y分量"""def __init__(self, x, y):self.x = x # 实部self.y = y # 虚部def __complex__(self):"""转换为复数时,x为实部,y为虚部"""return complex(self.x, self.y)# 测试
v1 = Vector(3, 4)
print(complex(v1)) # 输出: (3+4j)(调用 __complex__ 方法)v2 = Vector(0, 5)
print(complex(v2)) # 输出: 5j
综合应用示例
以下实现一个类可以同时实现多个数值转换方法,使其能灵活地参与各种数值操作:
class DataValue:"""数据值类,支持多种数值转换"""def __init__(self, value):self.value = valuedef __int__(self):return int(round(self.value)) # 四舍五入为整数def __float__(self):return float(self.value)def __bool__(self):return self.value != 0 # 非零值为Truedef __complex__(self):return complex(self.value, self.value * 0.5) # 虚部为实部的一半# 测试
data = DataValue(3.7)
print(int(data)) # 输出: 4
print(float(data)) # 输出: 3.7
print(bool(data)) # 输出: True
print(complex(data)) # 输出: (3.7+1.85j)data2 = DataValue(0)
print(bool(data2)) # 输出: False
11 使用建议
- 谨慎使用:只在确实需要时实现魔术方法
- 保持一致性:
- 实现
__eq__
时也应实现__hash__
- 实现比较运算符时最好实现全套
- 实现
- 性能考虑:魔术方法会被频繁调用,应保持高效
- 文档说明:明确记录每个魔术方法的行为
- 避免过度使用:不是所有类都需要成为"全能选手"