目录

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__,自动推导其他比较方法

练习

  1. Book 类实现 __str____repr__ 方法。
  2. 实现一个 Money 类,支持 +-* 运算和比较操作。
  3. 创建一个 Deck 类表示一副扑克牌,实现 __len____getitem__shuffle() 方法。
  4. 实现一个 Temperature 类,支持摄氏度和华氏度的转换和比较。
  5. 解释 __getattr____getattribute__ 的区别。