Python如何实现可在类内部调用的全局类映射主列表,解决循环导入问题
问题原因
这是典型的循环导入报错:你导入b.ba.Ba时,会先执行b/ba/__init__.py的顶层代码,第一行就触发utils模块导入。此时跳转到utils模块执行顶层代码,虽然utils里写了import b.ba,但此时b/ba/__init__.py还没执行完,Ba类还没完成定义,b.ba模块也没有挂载到b模块上,访问b.ba.Ba自然会抛出属性不存在的错误。
可行解决方法
方法1:调整类内utils的导入位置(改动最小)
不要在类文件的顶层导入utils,把导入逻辑放到实际调用find_class的方法内部即可:
# b/ba/__init__.py 修改后代码 class Ba(): def mycall(self, s): import utils return utils.find_class(s)
这种方式下,导入Ba类的时候不会立刻触发utils导入,只有第一次调用mycall方法时才会加载utils,此时b.ba已经完全加载完成,utils的MAPPING可以正常访问到b.ba.Ba。
方法2:映射表懒加载(更推荐,无需修改所有类文件)
修改utils的逻辑,不要在模块顶层直接构建MAPPING,延迟到第一次调用find_class时再构建映射表:
# utils/__init__.py 修改后代码 MAPPING = None def _build_mapping(): global MAPPING if MAPPING is not None: return # 所有依赖导入和映射表构建都放到这个函数内部 import a import a.aa import a.ab import b import b.ba import b.bb import c import c.ca import c.ca.caa MAPPING = { "Foobar": a.A, "Apple": a.aa.Aa, "Banana": a.ab.Ab, "Clementine": b.ba.Ba, "Granola": b.B, "Donut": b.bb.Bb, "Hashbrowns": c.C, "Egg": c.ca.Ca, "Furniture": c.ca.caa.Caa, } def find_class(s): _build_mapping() return MAPPING[s]
这种方案不需要调整各个类文件的导入逻辑,所有改动都集中在utils模块,维护成本更低。
方法3:动态导入类(彻底避免循环导入)
利用Python标准库的importlib做动态导入,MAPPING只存类的路径字符串,调用时再加载对应类,完全避免顶层导入依赖:
# utils/__init__.py 修改后代码 from importlib import import_module MAPPING = { "Foobar": ("a", "A"), "Apple": ("a.aa", "Aa"), "Banana": ("a.ab", "Ab"), "Clementine": ("b.ba", "Ba"), "Granola": ("b", "B"), "Donut": ("b.bb", "Bb"), "Hashbrowns": ("c", "C"), "Egg": ("c.ca", "Ca"), "Furniture": ("c.ca.caa", "Caa"), } def find_class(s): module_path, class_name = MAPPING[s] module = import_module(module_path) return getattr(module, class_name)
这种方案不受导入顺序影响,后续新增映射类也不需要提前在utils顶层导入,适合映射表规模大、迭代频繁的场景。
内容的提问来源于stack exchange,提问作者wherezyurheadat
相关产品推荐
相关产品推荐

