(十三)让书架参与 len、遍历与成员检查
1. 长度、遍历与成员检查
# 用语言约定的方法,把自己的容器接到已有操作上。
class BookShelf:
def __init__(self, titles):
self._titles = list(titles)
def __len__(self):
return len(self._titles)
def __iter__(self):
return iter(self._titles)
def __contains__(self, title):
return title in self._titles
shelf = BookShelf(["Python", "NumPy"])
print(len(shelf), "Python" in shelf)
print(list(shelf))
print(list(shelf))
print(bool(BookShelf([])))输出 2 True、两次 ['Python', 'NumPy'],以及 False。len(shelf) 调用 __len__;iter(shelf) 与 for 取得 __iter__ 返回的迭代器;in 使用 __contains__。没有单独定义 __bool__ 时,这个对象的真假判断继续使用长度,长度为零得到假。
__iter__ 里每次对列表建立新的迭代器,所以两次 list(shelf) 都能从头读取。若把一个迭代器建立后一直存着并重复交回,第二次读取就会接着原来的进度,甚至已经耗尽。对象提供容器式操作时,要把“数据集合”与“这次遍历位置”分开。
这些双下划线方法属于特殊方法,让对象响应语言中的已有操作。使用时正常写 len(shelf)、for title in shelf,按需要实现对应的方法即可。Python 数据模型
2. 用特殊方法响应加法
同一个符号也可以交给不同类型实现具体动作。整数的 + 计算数值,列表的 + 连接元素;自己定义的类型可以通过 __add__ 响应加法,这叫运算符重载。
# 阅读时长对象保存分钟数,加法产生一份新的合计记录。
class ReadingMinutes:
def __init__(self, value):
self.value = value
def __add__(self, other):
# 不能处理这种对象时,把结果交回 Python 的运算分派。
if not isinstance(other, ReadingMinutes):
return NotImplemented
return ReadingMinutes(self.value + other.value)
first = ReadingMinutes(20)
second = ReadingMinutes(35)
combined = first + second
print(combined.value)
print(first.value, second.value)输出 55 和 20 35。这里沿 first + second → first.__add__(second) → ReadingMinutes(55) 得到新对象,原来的两个对象保持各自的分钟数。
NotImplemented 是交给运算分派的特殊返回值,Python 可以继续尝试另一侧相应的运算方法;双方都没有提供可用实现时,加法会报告 TypeError。前面的 NotImplementedError 则是通过 raise 抛出的异常,它们的用途不同。
后面 NumPy 的数组操作和 LangChain 的 prompt | model | parser 都要看对应类型怎样实现符号。遇到它们时,先确认左右对象,再跟踪操作产生的结果。算术特殊方法