下载工作台
Python 编程实战

魔法方法与运算符重载

试读上半部分 · 解锁后可读全文

第 7 章 · 魔法方法与运算符重载

本章目标:理解 Python 双下划线方法(dunder) 的调用时机;掌握 __str__ / __repr__、比较与哈希、容器协议、上下文管理器与 __call__;能为 shop-demoProductCart 写出「Python 风格」的类;知道何时该重载、何时不该过度设计。

学时建议:3~4 小时(含 1 小时跟练)

前置ch06 面向对象(类、__init__@property)。


7.1 什么是魔法方法

Python 在特定操作时自动调用__ 开头和结尾的方法,例如:

你写的代码Python 实际调用
print(obj)obj.__str__()
repr(obj)obj.__repr__()
len(cart)cart.__len__()
a == ba.__eq__(b)
for x in cartcart.__iter__()
with open(...)__enter__ / __exit__
用户代码          魔法方法层              内置行为
print(cart)  →  cart.__str__()   →  控制台可读字符串
cart["BK-1"] →  cart.__getitem__ →  像字典一样取值

原则:魔法方法让自定义类融入 Python 语法;不是「炫技」,而是让 API 更自然。


7.2 __str__ 与 __repr__

方法受众目标
__str__终端用户 / 日志可读、简短
__repr__开发者 / 调试尽量能 eval 重建对象,至少含类名与关键字段
class Product:
    def __init__(self, sku: str, title: str, price: float):
        self.sku = sku
        self.title = title
        self.price = price

    def __str__(self) -> str:
        return f"{self.title}(¥{self.price:.2f})"

    def __repr__(self) -> str:
        return f"Product(sku={self.sku!r}, title={self.title!r}, price={self.price})"


p = Product("BK-001", "Python 基础", 59.9)
print(str(p))   # Python 基础(¥59.90)
print(repr(p))  # Product(sku='BK-001', title='Python 基础', price=59.9)

最佳实践:至少实现 __repr__;容器类在 __repr__ 里展示子项摘要。


7.3 比较运算:__eq__、__lt__ 与 __hash__

相等 __eq__

默认比较对象 id(同一实例才相等)。业务上常按 sku 判等:

def __eq__(self, other: object) -> bool:
    if not isinstance(other, Product):
        return NotImplemented
    return self.sku == other.sku

排序 __lt__

配合 sorted() / @total_ordering

from functools import total_ordering

@total_ordering
class Product:
    # ... __init__ ...

    def __lt__(self, other: "Product") -> bool:
        if not isinstance(other, Product):
            return NotImplemented
        return self.price < other.price

哈希 __hash__

对象作为 dict 键set 元素 时需可哈希;可变对象若定义了 __eq__,通常应设 __hash__ = None(不可哈希),或保证「相等对象哈希相同」且字段不可变。

# 不可变 sku 作键的简化写法(教学用)
def __hash__(self) -> int:
    return hash(self.sku)
场景建议
按 sku 去重__eq__ + __hash__ 基于 sku
可变购物车行不要放进 set;或只用 id 哈希

7.4 容器协议:__len__、__getitem__、__iter__

Cart 支持 len()、下标访问与 for 循环:

class Cart:
    def __init__(self):
        self._lines: list[tuple[Product, int]] = []

    def add(self, product: Product, qty: int = 1) -> None:
        self._lines.append((product, qty))

    def __len__(self) -> int:
        return len(self._lines)

    def __getitem__(self, index: int):
        return self._lines[index]

    def __iter__(self):
        return iter(self._lines)

    def total(self) -> float:
        return sum(p.price * q for p, q in self._lines)
cart = Cart()
cart.add(p, 2)
print(len(cart))       # 1
print(cart[0][0].title)
for product, qty in cart:
    print(product, qty)

7.5 上下文管理器:__enter__ 与 __exit__

with 语句保证进入/退出成对执行,适合锁、文件、数据库连接:

class OrderWriter:
    """向文件追加订单行,退出时 flush。"""

    def __init__(self, path: str):
        self.path = path
        self._file = None

    def __enter__(self):

以下内容需解锁后阅读

试读已结束。解锁本章 ¥5.00,或开通年度会员畅读全部教程。
年度会员 ¥199.00/年; 小紫 AI 工作台有效会员 ¥99.00/年

正文仅在服务端鉴权后下发,未付费无法获取下半部分内容。