在一个包中,如果A的完全导入依赖于B的导入,但是B的完全导入又需要导入A,即两个模块之间存在相互导入,那么就会引发循环导入问题。
在python2中,循环导入是不允许的,会直接触发ImportError。但是在python3中,python重写了导入机制,使得循环导入允许发生,但是会存在更隐晦的问题。
在python3中,只要执行import a后者from . import a,即只要a在import语句之后且被执行,那么a就会被加入到sys.modules中,而后续尽管在模块a被完全执行完毕之前,有模块b导入a,由于a已经在sys.modules中,b会直接从缓存中导入a,而不是重新执行a,因此这时避免了语法上无限循环导入的问题,因此导入语句可以顺利执行。但是由于b导入a时,a可能还没有被完全执行,即a还只是部分执行,所以有些没有被执行到的属性还不存在,那么这时如果b导入a,且调用了a中还没有被执行到的属性,就会触发AttributeError:模块a中缺少如此属性。这便是python3中允许循环导入语法而产生的一个更为隐晦的问题。
那么如何解决这个问题呢?
1. 重新设计。模块直接存在相互导入,可能存在设计不合理,此时可以merge模块,或者进行更好的抽象;
2. 导入语句只导入相对顶层的模块,不进一步导入子模块,后续在使用的时候通过子模块获取属性;
3. 在需要的时候导入,此时导入可能发生在函数中或者类中,而不是模块顶层,这时就不会在模块被导入时执行,从而可以避免属性不存在问题;
4. 修改初始化文件__init__.py,由于导入包时,实际上在执行该初始化文件,可以剔除非必要初始化,减少顶层模块导入引起的子模块(模块中同时导入了顶层模块)导入。