Python 魔术方法
目录
学习目标
- 理解魔术方法(特殊方法)的概念和作用
- 掌握常用的魔术方法:
__str__、__repr__、__eq__、__len__ - 学会运算符重载
- 理解可调用对象和上下文相关的魔术方法
一、什么是魔术方法
魔术方法(Magic Methods)也称为特殊方法(Special Methods)或双下划线方法(Dunder Methods),是 Python 中以双下划线 __ 开头和结尾的方法。它们在特定场景下被 Python 解释器自动调用。
class MyClass:
def __init__(self): # 构造方法
pass
def __str__(self): # str() 或 print() 时调用
pass
def __repr__(self): # repr() 或交互式环境调用
pass
def __eq__(self, other): # == 运算符
pass二、字符串表示
__str__ 和 __repr__
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
"""用户友好的字符串表示(print 使用)"""
return f"Person(name='{self.name}', age={self.age})"
def __repr__(self):
"""开发者友好的字符串表示(调试使用)"""
return f"Person('{self.name}', {self.age})"
p = Person("Alice", 25)
print(str(p)) # Person(name='Alice', age=25)
print(repr(p)) # Person('Alice', 25)
print(p) # Person(name='Alice', age=25)区别:
__str__:面向用户,可读性好__repr__:面向开发者,准确、无歧义, ideally 应该能直接eval()
三、比较运算符
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
# 等于
def __eq__(self, other):
if not isinstance(other, Rectangle):
return NotImplemented
return self.area() == other.area()
# 不等于(通常只需定义 __eq__,Python 会自动处理)
# def __ne__(self, other): ...
# 小于
def __lt__(self, other):
if not isinstance(other, Rectangle):
return NotImplemented
return self.area() < other.area()
# 小于等于
def __le__(self, other):
return self == other or self < other
# 大于(Python 会自动推导)
# def __gt__(self, other): ...
# 大于等于(Python 会自动推导)
# def __ge__(self, other): ...
def __str__(self):
return f"Rectangle({self.width}x{self.height}, area={self.area()})"
r1 = Rectangle(3, 4) # area = 12
r2 = Rectangle(2, 6) # area = 12
r3 = Rectangle(4, 5) # area = 20
print(r1 == r2) # True(面积相等)
print(r1 < r3) # True
print(r3 > r1) # True(Python 自动推导)
print(r1 <= r2) # True
# 排序
rects = [r3, r1, Rectangle(1, 1)]
rects.sort()
for r in rects:
print(r)四、运算符重载
算术运算符
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
"""+ 运算符"""
if isinstance(other, Vector):
return Vector(self.x + other.x, self.y + other.y)
return NotImplemented
def __sub__(self, other):
"""- 运算符"""
if isinstance(other, Vector):
return Vector(self.x - other.x, self.y - other.y)
return NotImplemented
def __mul__(self, scalar):
"""* 运算符(数乘)"""
if isinstance(scalar, (int, float)):
return Vector(self.x * scalar, self.y * scalar)
return NotImplemented
def __rmul__(self, scalar):
"""右乘(处理 3 * v 的情况)"""
return self * scalar
def __neg__(self):
"""负号运算符"""
return Vector(-self.x, -self.y)
def __abs__(self):
"""abs() 函数"""
return (self.x ** 2 + self.y ** 2) ** 0.5
def __str__(self):
return f"Vector({self.x}, {self.y})"
def __repr__(self):
return f"Vector({self.x}, {self.y})"
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2) # Vector(4, 6)
print(v1 - v2) # Vector(-2, -2)
print(v1 * 3) # Vector(3, 6)
print(3 * v1) # Vector(3, 6)(__rmul__)
print(-v1) # Vector(-1, -2)
print(abs(v1)) # 2.236...容器相关运算符
class MyList:
def __init__(self, items=None):
self.items = items or []
def __len__(self):
"""len() 函数"""
return len(self.items)
def __getitem__(self, index):
"""索引访问:obj[index]"""
return self.items[index]
def __setitem__(self, index, value):
"""索引赋值:obj[index] = value"""
self.items[index] = value
def __delitem__(self, index):
"""del obj[index]"""
del self.items[index]
def __contains__(self, item):
"""in 运算符"""
return item in self.items
def __iter__(self):
"""迭代"""
return iter(self.items)
def append(self, item):
self.items.append(item)
ml = MyList([1, 2, 3])
print(len(ml)) # 3
print(ml[0]) # 1
ml[0] = 10
print(ml[0]) # 10
print(2 in ml) # True
for item in ml:
print(item, end=" ") # 10 2 3五、可调用对象
class Counter:
def __init__(self, start=0):
self.count = start
def __call__(self, step=1):
"""使实例可以像函数一样调用"""
self.count += step
return self.count
def __str__(self):
return f"Counter({self.count})"
c = Counter(10)
print(c()) # 11
print(c()) # 12
print(c(5)) # 17
print(c(3)) # 20
# 检查是否可调用
print(callable(c)) # True
print(callable(10)) # False六、属性访问控制
class ProtectedAttr:
def __init__(self):
self._data = {}
def __getattr__(self, name):
"""访问不存在的属性时调用"""
print(f"属性 '{name}' 不存在,返回默认值")
return None
def __getattribute__(self, name):
"""访问任何属性时调用(优先于 __getattr__)"""
# 注意:不要在这里访问 self.xxx,会递归!
return super().__getattribute__(name)
def __setattr__(self, name, value):
"""设置属性时调用"""
if name.startswith('_'):
super().__setattr__(name, value)
else:
print(f"设置属性 '{name}' = {value}")
super().__setattr__(name, value)
def __delattr__(self, name):
"""删除属性时调用"""
print(f"删除属性 '{name}'")
super().__delattr__(name)
obj = ProtectedAttr()
print(obj.name) # 属性 'name' 不存在,返回默认值
obj.age = 25 # 设置属性 'age' = 25
print(obj.age) # 25七、常用魔术方法速查表
| 类别 | 魔术方法 | 触发场景 |
|---|---|---|
| 构造/析构 | __init__ |
创建实例后初始化 |
__del__ |
实例被销毁前 | |
| 字符串 | __str__ |
str(), print() |
__repr__ |
repr(), 交互式环境 |
|
| 比较 | __eq__ |
== |
__lt__ |
< |
|
__le__ |
<= |
|
__gt__ |
> |
|
__ge__ |
>= |
|
| 算术 | __add__ |
+ |
__sub__ |
- |
|
__mul__ |
* |
|
__truediv__ |
/ |
|
__floordiv__ |
// |
|
__mod__ |
% |
|
__pow__ |
** |
|
| 容器 | __len__ |
len() |
__getitem__ |
obj[key] |
|
__setitem__ |
obj[key] = val |
|
__delitem__ |
del obj[key] |
|
__contains__ |
in |
|
__iter__ |
for 循环 |
|
| 可调用 | __call__ |
obj() |
| 属性 | __getattr__ |
访问不存在的属性 |
__setattr__ |
设置属性 | |
__delattr__ |
删除属性 | |
| 类型转换 | __int__ |
int(obj) |
__float__ |
float(obj) |
|
__bool__ |
bool(obj) |
八、综合示例
"""
自定义数值类:实现一个支持四则运算的分数类
"""
import math
class Fraction:
def __init__(self, numerator, denominator=1):
if denominator == 0:
raise ValueError("分母不能为 0")
# 约分
g = math.gcd(abs(numerator), abs(denominator))
self.numerator = numerator // g
self.denominator = denominator // g
# 处理负号
if self.denominator < 0:
self.numerator = -self.numerator
self.denominator = -self.denominator
def __str__(self):
if self.denominator == 1:
return str(self.numerator)
return f"{self.numerator}/{self.denominator}"
def __repr__(self):
return f"Fraction({self.numerator}, {self.denominator})"
def __eq__(self, other):
if isinstance(other, Fraction):
return (self.numerator == other.numerator and
self.denominator == other.denominator)
if isinstance(other, int):
return self.denominator == 1 and self.numerator == other
return NotImplemented
def __add__(self, other):
if isinstance(other, int):
other = Fraction(other)
if isinstance(other, Fraction):
n = self.numerator * other.denominator + other.numerator * self.denominator
d = self.denominator * other.denominator
return Fraction(n, d)
return NotImplemented
def __radd__(self, other):
return self + other
def __sub__(self, other):
if isinstance(other, int):
other = Fraction(other)
if isinstance(other, Fraction):
n = self.numerator * other.denominator - other.numerator * self.denominator
d = self.denominator * other.denominator
return Fraction(n, d)
return NotImplemented
def __mul__(self, other):
if isinstance(other, int):
other = Fraction(other)
if isinstance(other, Fraction):
return Fraction(self.numerator * other.numerator,
self.denominator * other.denominator)
return NotImplemented
def __truediv__(self, other):
if isinstance(other, int):
other = Fraction(other)
if isinstance(other, Fraction):
return Fraction(self.numerator * other.denominator,
self.denominator * other.numerator)
return NotImplemented
def __float__(self):
return self.numerator / self.denominator
# 使用
f1 = Fraction(1, 2)
f2 = Fraction(1, 3)
print(f"{f1} + {f2} = {f1 + f2}") # 1/2 + 1/3 = 5/6
print(f"{f1} - {f2} = {f1 - f2}") # 1/2 - 1/3 = 1/6
print(f"{f1} * {f2} = {f1 * f2}") # 1/2 * 1/3 = 1/6
print(f"{f1} / {f2} = {f1 / f2}") # 1/2 / 1/3 = 3/2
print(f"{f1} + 1 = {f1 + 1}") # 1/2 + 1 = 3/2
print(f"1 + {f1} = {1 + f1}") # 1 + 1/2 = 3/2
print(f"float({f1}) = {float(f1)}") # 0.5
print(f"Fraction(2, 4) == Fraction(1, 2): {Fraction(2, 4) == Fraction(1, 2)}") # True小结
- 魔术方法在特定场景下被 Python 自动调用
__str__面向用户,__repr__面向开发者- 运算符重载让自定义类支持
+、-、*等运算符 __len__、__getitem__等让类支持容器协议__call__让实例可以像函数一样调用- 使用
functools.total_ordering可以只定义__eq__和__lt__,自动推导其他比较方法
练习
- 为
Book类实现__str__和__repr__方法。 - 实现一个
Money类,支持+、-、*运算和比较操作。 - 创建一个
Deck类表示一副扑克牌,实现__len__、__getitem__和shuffle()方法。 - 实现一个
Temperature类,支持摄氏度和华氏度的转换和比较。 - 解释
__getattr__和__getattribute__的区别。