Python模块级__getattr__导入时触发两次运行的原因及解决方法
两次触发模块级
__getattr__的原因与规避方案 核心触发原因
模块级__getattr__是Python 3.7新增的模块属性查找兜底逻辑,它的触发规则是:每次在模块的__dict__中找不到目标属性时,就会执行一次,没有内置的单次缓存机制。
结合你给出的代码和运行效果截图,两次执行的来源非常明确:
- 执行
from test import x, y语句时,Python导入系统会在完成test模块的整体加载后,依次对要导入的x、y两个名字做独立的属性查找 - 由于你的
test.py中没有把x、y定义在模块顶层,两次查找都无法在模块__dict__中命中,会分别触发一次__getattr__调用,最终输出两行导入日志,总共执行两次逻辑,完全符合解释器的正常运行流程。
除此之外还有一种更隐蔽的双触发场景:当你尝试只导入单个名字(比如from test import x)时,如果__getattr__内部没有把加载到的属性值写回模块__dict__,部分Python版本的导入校验逻辑会对同一个名字做两次属性合法性检查,也会导致同一段逻辑被执行两次。
常见规避方法
- 先明确特性边界:模块级
__getattr__不是模块加载的全局初始化钩子,不要把需要全局仅执行一次的逻辑完全堆在这个函数里,否则只要发生多次属性查找,逻辑就会被重复触发。如果需要在模块导入时执行一次全局自定义逻辑,直接把代码写在模块的顶层作用域即可,这部分代码天生就只会在模块首次加载时运行一次。 - 给懒加载的属性加上缓存:在
__getattr__内部完成对象加载后,把对应的值写回模块的__dict__,后续查找同一个属性时会直接从字典中取值,不会再触发__getattr__逻辑,示例代码:# test.py import sys def __getattr__(name: str): print(f"Imported {name}") # 实际加载属性的逻辑 real_value = 自定义属性加载逻辑(name) # 把值写入模块__dict__实现缓存 sys.modules[__name__].__dict__[name] = real_value return real_value - 如果担心重复执行自定义业务逻辑,可以在模块内维护一个已处理名字的集合,命中集合就直接返回缓存值,不重复执行业务逻辑:
# test.py import sys _processed = set() def __getattr__(name: str): if name in _processed: return sys.modules[__name__].__dict__.get(name) print(f"Imported {name}") _processed.add(name) # 后续加载属性的逻辑
内容的提问来源于stack exchange,提问作者pyjamas
相关产品推荐
相关产品推荐

