强类型只读描述符是基于描述符协议、不可变返回值和类型提示构建的安全只读窗口,用于迭代器上下文:不实现__set__、返回tuple等不可变类型、标注ReadOnly[T],确保外部仅能安全访问当前迭代状态。

强类型只读描述符本身不是 Python 内置概念,但可以基于描述符协议(__get__、__set__、__delete__)配合类型提示与不可变语义,为迭代器上下文构建一个**安全的外部只读窗口**——即:外部代码能访问迭代状态或数据,但无法修改内部结构、不破坏迭代一致性,也不绕过协议约束。
明确“只读”的边界:不是禁止所有写,而是控制写入口
Python 中没有真正意义上的“强制只读”字段,所谓“只读”是通过约定+协议+类型系统协同实现的:
-
描述符中不实现
__set__:访问时触发__get__返回值,但赋值会抛出AttributeError(默认行为); -
返回值本身为不可变类型:如
tuple、frozenset、NamedTuple或自定义__slots__ = ()的类,避免浅层只读、深层可变; -
类型注解标注
ReadOnly[T](需配合 mypy 或 pyright):虽不运行时生效,但能拦截误用,例如:
class IteratorView:
_data: list[int]
def __init__(self, data: list[int]): self._data = data
@property
def items(self) -> tuple[int, ...]: # 显式只读类型
return tuple(self._data)
为迭代器对象提供只读视图描述符
不直接暴露原始迭代器或可变容器,而是用描述符封装一个“快照式”或“代理式”只读接口:
-
快照型:在访问时生成不可变副本(适合小数据、低频访问),例如:
class ReadOnlySnapshot:
def __get__(self, obj, owner):
if obj is None: return self
return tuple(obj._buffer) # 假设 _buffer 是可变列表 -
代理型:返回一个仅实现
__iter__和__len__但无append/__setitem__的包装器,例如:
class ReadOnlyIteratorView:
def __get__(self, obj, owner):
return ReadOnlySeq(obj._buffer) - 关键点:该描述符应绑定在迭代器类(如自定义
CountDownIterator)实例上,而非作为类属性泛化使用,确保视图与当前迭代状态一致。
结合迭代器协议强化安全性
只读窗口必须尊重迭代器生命周期,不能脱离 __next__ 的推进逻辑:
- 描述符返回的只读序列不应允许跳转或重置(即不暴露
__getitem__索引访问,除非明确设计为随机读且不干扰状态); - 若迭代器有内部游标(如
self.current),只读视图应反映「当前已产出」的部分,而非全部原始数据; - 可在描述符
__get__中检查迭代器是否已耗尽(StopIteration已被抛出),并返回空元组或引发对应提示,避免陈旧数据误导。
实际效果示例
假设你有一个生产者-消费者场景中的迭代器,外部模块只需观察已产出项:
- 消费者拿到的是
it.readonly_history—— 类型为tuple[int, ...],长度随next(it)逐步增长; - 尝试
it.readonly_history += (42,)会失败(tuple不支持+=); - 尝试
it.readonly_history[0] = 999会失败(tuple不可变); - 类型检查器会拒绝将该属性赋给期望
list[int]的参数,提前捕获误用。

















