Appearance
28|魔术方法与属性
Python 类中有一些以双下划线开头和结尾的特殊方法,叫魔术方法(magic methods)或 dunder methods(double underscore)。它们不需要直接调用,而是在特定场景下由 Python 自动触发。理解常用的魔术方法,能让自定义类更像内置类型一样工作。
一、__str__ 与 __repr__
__str__ 定义对象的友好字符串表示(给用户看),__repr__ 定义官方字符串表示(给开发者看):
python
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"({self.x}, {self.y})"
def __repr__(self):
return f"Point({self.x}, {self.y})"
p = Point(3, 4)
print(str(p)) # (3, 4)
print(repr(p)) # Point(3, 4)print() 调用 __str__,交互式解释器直接显示对象时调用 __repr__。如果只有 __repr__ 没有 __str__,__str__ 会回退到 __repr__。
好的 __repr__ 应该返回能通过 eval() 重建对象的字符串(如果可能)。
二、__len__
让对象支持 len():
python
class Group:
def __init__(self):
self.members = []
def add(self, name):
self.members.append(name)
def __len__(self):
return len(self.members)
g = Group()
g.add("Alice")
g.add("Bob")
print(len(g)) # 2三、__eq__、__lt__ 等比较方法
让对象支持比较运算:
python
class Student:
def __init__(self, name, score):
self.name = name
self.score = score
def __eq__(self, other):
return self.score == other.score
def __lt__(self, other):
return self.score < other.score
def __repr__(self):
return f"Student({self.name!r}, {self.score})"
alice = Student("Alice", 85)
bob = Student("Bob", 90)
print(alice == bob) # False
print(alice < bob) # True
print(sorted([bob, alice])) # [Student('Alice', 85), Student('Bob', 90)]实现了 __eq__ 和 __lt__ 后,sorted()、min()、max() 都能直接处理这个类的对象。
如果逐个实现比较方法太麻烦,可以用 functools.total_ordering 装饰器——只要实现 __eq__ 和任意一个顺序方法(__lt__、__le__、__gt__、__ge__),其余方法会自动生成:
python
from functools import total_ordering
@total_ordering
class Student:
def __init__(self, name, score):
self.name = name
self.score = score
def __eq__(self, other):
return self.score == other.score
def __lt__(self, other):
return self.score < other.score四、__getitem__、__setitem__
让对象支持索引访问:
python
class GradeBook:
def __init__(self):
self._grades = {}
def __getitem__(self, name):
return self._grades.get(name, 0)
def __setitem__(self, name, score):
self._grades[name] = score
book = GradeBook()
book["Alice"] = 85 # 调用 __setitem__
print(book["Alice"]) # 85,调用 __getitem__
print(book["Bob"]) # 0,默认值五、__contains__
让对象支持 in 判断:
python
class Group:
def __init__(self):
self.members = []
def add(self, name):
self.members.append(name)
def __contains__(self, name):
return name in self.members
g = Group()
g.add("Alice")
print("Alice" in g) # True六、__iter__ 与 __next__
让对象支持 for 循环:
python
class Countdown:
def __init__(self, start):
self.start = start
def __iter__(self):
return self
def __next__(self):
if self.start <= 0:
raise StopIteration
self.start -= 1
return self.start + 1
for num in Countdown(5):
print(num)
# 5, 4, 3, 2, 1实现 __iter__ 和 __next__ 的对象是迭代器。更简单的方式是用生成器:
python
class Countdown:
def __init__(self, start):
self.start = start
def __iter__(self):
n = self.start
while n > 0:
yield n
n -= 1七、@property
把方法当成属性访问:
python
class Circle:
def __init__(self, radius):
self.radius = radius
@property
def area(self):
return 3.14159 * self.radius ** 2
@property
def diameter(self):
return self.radius * 2
c = Circle(5)
print(c.area) # 78.53975,像属性一样访问,不加括号
print(c.diameter) # 10@property 把方法变成只读属性。需要可写时,加 setter:
python
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, value):
if value < 0:
raise ValueError("半径不能为负数")
self._radius = value
c = Circle(5)
c.radius = 10 # 调用 setter
print(c.radius) # 10@property 适合封装——外部像访问属性一样自然,内部可以加入校验逻辑。
记忆锚点:__str__ 给 str()/print() 用,__repr__ 给交互式环境用;__len__ 支持 len();__eq__/__lt__ 支持比较;__getitem__/__setitem__ 支持索引;__contains__ 支持 in;__iter__/__next__ 支持 for 循环;@property 把方法变成属性访问,可加 setter 实现可写。