编程后端开发工程师

解释一下 Python 中的 'Sequence' 协议,它是如何实现的,以及它与普通的可迭代对象有何不同?

用 Hintsage AI 助手通过面试

答案。

在 Python 中,'Sequence' 协议定义了可索引和可迭代对象的接口,例如列表或元组。历史上,序列几乎在 Python 的早期版本中就出现了,以支持对数据结构的自然操作:索引、切片、元素遍历。

问题 - 为了让用户定义的类表现得像一个序列,仅仅实现 iternext 方法是不够的。为了完全支持序列行为,需要额外的方法。

解决方案 - 要实现自定义的序列类型,需要定义 getitem 方法(这是索引和切片所必需的),并可选地定义 len 方法(用于 len() 和检查长度)。这样,对象将支持迭代、索引访问、切片操作,以及许多 Python 对序列的标准操作。

代码示例:

class MyCounter: def __init__(self, stop): self._stop = stop def __getitem__(self, index): if 0 <= index < self._stop: return index * 10 else: raise IndexError('超出范围') def __len__(self): return self._stop c = MyCounter(5) print(c[3]) # 30 print(len(c)) # 5 for x in c: print(x)

关键特性:

  • 对象通过 getitem 支持索引访问和切片。
  • 要支持 len() 函数,需要 len
  • 如果 iter 没有定义,则迭代基于 getitem 进行。

有陷阱的问题。

如果我仅实现 iternext,我的对象会成为一个序列(Sequence)吗?

不。这样的对象只是一个可迭代对象,但不是一个序列。它不支持索引、切片和标准的类似列表的对象功能。

一定要实现 getitem 才能支持 for 循环吗?

不一定。如果实现了 iter,for 循环将会工作。但如果没有 iter,解释器会尝试使用 getitem,从索引 0 开始,直到出现 IndexError。因此,序列只需 getitem 就足够了。

可以仅为 int 实现 getitem,而不为 slice 吗?

从技术上讲,对 c[0] 的访问是可以的,但尝试获取切片 c[1:4] 将会失败。为了支持切片,getitem 必须能够处理 slice 类型的对象(请参阅 slice.indices 和 isinstance(key, slice))。

代码示例:

class S: def __getitem__(self, idx): if isinstance(idx, slice): return [x for x in range(idx.start or 0, idx.stop or 10, idx.step or 1)] return idx * 2

常见错误和反模式

  • 仅定义 iter,期望对象成为完整的序列。
  • 实现了 getitem,但没有处理 slice 对象。
  • 没有实现 len,导致 len(obj) 引发 TypeError。

生活中的例子

负面案例

实现了自定义结构,仅定义了 iter,以为现在可以使用切片和索引。

优点:

  • 在 for 循环中工作,支持生成器。

缺点:

  • 不支持语法 obj[5] 或 obj[1:3],出现错误。
  • len(obj) 也不起作用。

正面案例

类实现了 getitem,支持切片和整数索引,以及 len

优点:

  • 支持所有序列操作:切片、索引、len、迭代。
  • 与标准库的集成(例如,random.choice)。

缺点:

  • 需要仔细实现切片的处理,否则在处理切片时可能会出现错误。