Skip to content

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 实现可写。