Python导入异常行为底层逻辑解惑:为何包命名空间超出预期?
Python包导入时命名空间自动扩展的底层原理
这个现象是Python导入系统的标准设计,和Python 3.9版本无关,所有主流Python版本都遵循该逻辑,核心原因如下:
导入系统核心规则
Python的所有导入操作都会优先查询sys.modules全局缓存,只要对应模块/包已经存在于缓存中,就不会重复执行加载逻辑,仅做命名空间绑定。
from countries import spain的完整执行步骤
- 第一步:加载父包
countries
先检查sys.modules中是否存在countries键,不存在则找到对应包目录,执行__init__.py生成包模块对象,存入sys.modules['countries'],此时该对象已包含greece属性(来自__init__.py的from . import greece声明)。 - 第二步:查找子模块
spain
从sys.modules['countries']的属性中查找spain,找不到时会自动加载countries/spain.py生成countries.spain模块对象,并存入sys.modules['countries.spain']。 - 第三步:自动绑定父包属性
子模块加载完成后,Python会自动将该子模块对象作为属性挂载到父包的模块对象上,等价于执行sys.modules['countries'].spain = sys.modules['countries.spain']的操作。 - 第四步:将
spain绑定到当前执行上下文的命名空间中。
后续import countries的执行步骤
此时sys.modules['countries']已经存在,Python不会重新执行countries包的__init__.py,只会直接把缓存中的countries模块对象绑定到当前上下文的countries变量上。
由于该模块对象已经在之前的导入操作中被挂载了spain属性,所以dir(countries)会同时返回greece和spain两个对象。
类似的常见场景:执行
import os.path后,此前已经导入的os模块也会自动拥有path属性,和你遇到的逻辑完全一致。
验证方法
你可以通过打印sys.modules['countries'].__dict__验证该逻辑:在执行from countries import spain前后分别打印该字典,可以看到spain键会在导入子模块后自动出现在父包的属性字典中。
内容的提问来源于stack exchange,提问作者Jeffery
相关产品推荐
相关产品推荐

