当前位置: 首页 > news >正文

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 + aa 不支持加法时,会调用 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 - ab 不支持减法时的情况。

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 * ab 不支持乘法时的情况。

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 表示 ab 次方。__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 objitem 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 数值类型转换相关魔术方法

数值类型转换魔术方法允许自定义类的实例支持内置数值类型(如 intfloatboolcomplex)的转换,通过实现这些方法,可以定义对象转换为特定数值类型时的行为,使自定义对象能更自然地参与数值运算和判断。

魔术方法对应转换函数调用时机主要作用
__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 语句、逻辑运算)被触发,返回 TrueFalse。如果未定义 __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 使用建议

  1. 谨慎使用:只在确实需要时实现魔术方法
  2. 保持一致性:
    • 实现__eq__时也应实现__hash__
    • 实现比较运算符时最好实现全套
  3. 性能考虑:魔术方法会被频繁调用,应保持高效
  4. 文档说明:明确记录每个魔术方法的行为
  5. 避免过度使用:不是所有类都需要成为"全能选手"
http://www.dtcms.com/a/344533.html

相关文章:

  • K8s的相关知识总结
  • X00238-非GNSS无人机RGB图像卫星图像视觉定位python
  • Django中间件自定义开发指南:从原理到实战的深度解析
  • 广播级讯道摄像机CCU后挂上的PGM、ENG、PROD音频旋钮是做什么用的?
  • js:beforeUnload这个方法能不能监听到关闭浏览器和刷新浏览器行为
  • 视觉语言大模型应用开发——基于 CLIP、Gemini 与 Qwen2.5-VL 的视频理解内容审核全流程实现
  • uniapp image标签展示视频第一帧
  • 【Linux】Vim编辑器:从入门到高效使用
  • MiniCPM-V4.0开源并上线魔乐社区,多模态能力进化,手机可用,还有最全CookBook!
  • WebRTC 结合云手机:释放实时通信与虚拟手机的强大协同效能
  • 聚焦科技前沿,华金证券与非凸科技共探数智交易新路径
  • 【GaussDB】全密态等值查询功能测试及全密态技术介绍
  • UNIKGQA论文笔记
  • SYBASE ASE、Oracle、MySQL/MariaDB、SQL Server及PostgreSQL在邮件/短信发送功能上的全面横向对比报告
  • 全景式综述|多模态目标跟踪全面解析:方法、数据、挑战与未来
  • #Datawhale 组队学习#8月-工作流自动化n8n入门-2
  • 基于51单片机的超声波液位检测OLED显示设计
  • MySQL InnoDB表空间深度解析:从原理到性能优化
  • Seaborn数据可视化实战:Seaborn与Plotly交互式图表入门
  • 图像处理中的伪影
  • ASPICE过程能力确定——度量框架
  • 美国对华科技政策思路变化:产业影响与投资逻辑解析
  • C/C++三方库移植到HarmonyOS平台详细教程
  • 2025年推理大模型有哪些以及优势对比
  • C++函数重载与引用详解
  • 线段树01
  • 合同差异智能比对,有效规避“阴阳合同”
  • 白名单过滤的文件上传如何bypass:boot2root靶机之fristileaks
  • 基于 SkyWalking + Elasticsearch + Grafana 的可落地调用链监控方案
  • 易混淆的CommonJS和ESM(ES Module)及它们区别