Generator适合按需加载,因其惰性求值特性可避免启动时资源浪费和命名空间污染,结合__getattr__或@property实现首次访问才加载、后续缓存复用。

在类初始化阶段用 Generator 封装私有按需加载逻辑,核心不是“炫技”,而是解决两个实际问题:一是避免一次性加载大量资源(如配置、数据、外部依赖)导致启动慢或内存浪费;二是防止这些内部资源直接暴露在实例命名空间中,造成属性名冲突或误用。
为什么 Generator 适合做按需加载封装
Generator 函数返回迭代器对象,本身不执行逻辑,只在首次调用 next() 或进入 for 循环时才触发计算。这个“惰性求值”特性天然契合“私有 + 按需”的设计目标:
- 它不把结果存为实例属性,避免 self.xxx 泄露到公开命名空间
- 每次调用都可独立控制加载时机和条件,比如首次访问才读文件、首次查询才连数据库
- 配合 __getattr__ 或属性描述符,能实现“像属性一样用,但背后是延迟加载”
用 __getattr__ + Generator 实现透明按需加载
这是最常用也最干净的方式:把加载逻辑藏在 Generator 里,仅当访问不存在的属性时才触发。
class ConfigLoader:
def __init__(self, config_path):
self._config_path = config_path
# 不预先加载,也不挂载任何 config 属性
<pre class='brush:php;toolbar:false;'>def _load_config_gen(self):
"""私有生成器:只在需要时解析一次"""
import json
with open(self._config_path) as f:
yield json.load(f) # 只 yield 一次,确保单次加载
def __getattr__(self, name):
if name.startswith('_') or name in self.__dict__:
raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")
# 首次访问任意公有属性(如 db_host)时,触发加载
if not hasattr(self, '_cached_config'):
# 调用 generator 并取其唯一产出
self._cached_config = next(self._load_config_gen())
return self._cached_config.get(name)使用示例
cfg = ConfigLoader("config.json") print(cfg.db_host) # 第一次访问:加载并返回 print(cfg.timeout) # 后续访问:直接从缓存字典取,不重复 IO
用 property + Generator 包装更明确的懒加载字段
如果某些字段语义清晰、使用频繁,更适合显式定义为 @property,内部用 Generator 控制加载节奏:
- Generator 在这里主要起“单次执行 + 状态隔离”作用,而非真正迭代
- 搭配 hasattr 或私有标记位,确保只加载一次
- 比纯函数调用更符合属性访问习惯,且 IDE 可识别
class DataRepository:
def __init__(self, source_uri):
self._source_uri = source_uri
self._data_gen = None
<pre class='brush:php;toolbar:false;'>@property
def records(self):
if self._data_gen is None:
# 初始化 generator(尚未执行)
self._data_gen = self._fetch_records_gen()
try:
# 取第一个批次(假设按页加载)
return next(self._data_gen)
except StopIteration:
return []
def _fetch_records_gen(self):
# 模拟分批读取大文件或 API 分页
page = 0
while True:
yield [f"record_{i}_page_{page}" for i in range(10)]
page += 1
if page >= 3: # 仅模拟 3 页
break
规避命名空间污染的关键细节
Generator 本身不会污染命名空间,但用法不当仍可能引入隐患:
- 不要把 generator 对象赋给 self.xxx(除非你明确需要多次迭代),否则它会成为公开可访问的属性
- 避免在 __init__ 中调用 list(gen) 或 tuple(gen) —— 这会立刻执行并占用内存,失去“按需”意义
- 私有方法名(如 _load_xxx_gen)加下划线前缀,配合 __all__ 或文档说明,明确传达“非公共接口”意图
- 若需多处复用同一加载结果,用 self._cached_xxx 缓存,而不是反复新建 generator

















