You need to enable JavaScript to run this app. Python模块级__getattr__导入时触发两次运行的原因及解决方法
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.28 09:36:07