同名不同路径模块的动态加载冲突解决:避免不同Mod目录下的cats.py被识别为同一模块
我完全懂你现在的困扰——不同Mod文件夹里的cats.py被Python的模块加载机制当成了同一个全局模块,导致导入第二个Mod时找不到对应的AnotherCat类。毕竟Python默认会缓存导入过的模块,不会重新去新目录找,而且你不想搞复杂的包结构,这个需求非常合理,Mod结构就是要简单才好写。
问题根源
Python的sys.modules字典会缓存所有已经导入的模块:当你从第一个Mod目录导入cats后,第二个Mod再执行import cats时,Python直接从缓存里拿第一个Mod的cats模块,根本不会去第二个Mod的目录下查找,自然就会出现module 'cats' has no attribute 'AnotherCat'的错误。
解决方案:两种可行的实现方式
下面的方案都不需要你给Mod目录加__init__.py搞包结构,完全适配你现在的文件布局。
方案1:临时修改sys.path + 清理模块缓存(简单易改,适配现有代码)
核心思路:加载每个Mod时,把当前Mod的目录临时加到sys.path的最前面(让Python优先从这里找模块),导入完成后立刻恢复sys.path,并删掉缓存里的模块,避免影响下一个Mod。
你需要修改iml_lib里的find_mod_classes或load_mod_info函数(就是你说的AI生成的扫描Mod类的函数),在扫描每个Mod的文件前,加上路径处理逻辑:
import sys import os import importlib def load_mod_info(mod_id: str): # 1. 定位当前Mod的目录 mod_dir = os.path.join("mods", mod_id) if not os.path.exists(mod_dir): raise ValueError(f"Mod directory {mod_dir} not found") # 2. 保存原始的sys.path,后面要恢复 original_sys_path = sys.path.copy() # 3. 把当前Mod目录加到sys.path最前面,让import优先找这里 sys.path.insert(0, mod_dir) try: # 4. 扫描Mod目录下的.py文件,找IceMod子类(这里是你原来的扫描逻辑) mod_classes = [] for file_name in os.listdir(mod_dir): if file_name.endswith(".py") and not file_name.startswith("_"): module_name = os.path.splitext(file_name)[0] imported_module = importlib.import_module(module_name) # 筛选出IceMod的子类 for attr_name in dir(imported_module): attr = getattr(imported_module, attr_name) if isinstance(attr, type) and issubclass(attr, iml_lib.IceMod) and attr != iml_lib.IceMod: mod_classes.append(attr) return mod_classes finally: # 5. 恢复原始sys.path,避免影响其他Mod sys.path = original_sys_path # 6. 清理缓存里的同名模块(比如cats、scripts),防止下一个Mod导入时用旧的 for module_name in ["cats", "scripts"]: if module_name in sys.modules: del sys.modules[module_name]
这个方案的好处是:你的Mod代码(比如ExampleMod类)完全不用改,还是可以写import cats,因为加载时sys.path已经被临时修改,会优先找当前Mod目录下的文件。
方案2:用importlib.util直接加载模块(更干净,无全局污染)
核心思路:不依赖全局的sys.path,直接指定模块文件的路径,给每个Mod的同名模块起一个唯一的名字(比如加上Mod的mod_id前缀,比如example-mod.iceandfire04.cats),这样每个Mod的cats模块都是独立的,不会冲突。
步骤1:在iml_lib里添加工具函数,加载Mod专属模块
import importlib.util import os import sys def load_mod_specific_module(mod_id: str, module_name: str) -> object: """加载指定Mod目录下的模块,返回模块对象""" mod_dir = os.path.join("mods", mod_id) module_file = os.path.join(mod_dir, f"{module_name}.py") if not os.path.exists(module_file): raise ValueError(f"Module file {module_file} not found for mod {mod_id}") # 给模块起唯一的名字,比如"example-mod.iceandfire04.cats" unique_module_name = f"{mod_id}.{module_name}" # 从文件加载模块 spec = importlib.util.spec_from_file_location(unique_module_name, module_file) if spec is None or spec.loader is None: raise ValueError(f"Could not create module spec for {module_file}") module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) # 把模块加到sys.modules里,方便后续引用 sys.modules[unique_module_name] = module return module
步骤2:修改Mod类,延迟加载专属模块
把modifications和scripts改成在__init__里初始化(避免类定义时就导入错误的模块):
class ExampleMod(iml_lib.IceMod): name = "Example Mod" mod_id = "example-mod.iceandfire04" def __init__(self): # 加载当前Mod专属的cats和scripts模块 cats = iml_lib.load_mod_specific_module(self.mod_id, "cats") scripts = iml_lib.load_mod_specific_module(self.mod_id, "scripts") self.modifications = [cats.CustomCat()] self.scripts = [scripts.AntibanScript()] def load(self, loader: tbcml.ModLoader, mod: tbcml.Mod): super().load(self, loader=loader, mod=mod) print("Example Mod: You can put custom loading code here if needed!")
步骤3:修改find_mod_classes函数,用同样方式加载Mod主文件
def find_mod_classes(selected_mods: list[str]) -> list[iml_lib.IceMod]: mods = [] for mod_id in selected_mods: mod_dir = os.path.join("mods", mod_id) # 遍历Mod目录下的.py文件,找IceMod子类 for file_name in os.listdir(mod_dir): if file_name.endswith(".py") and not file_name.startswith("_"): unique_mod_name = f"{mod_id}.{os.path.splitext(file_name)[0]}" spec = importlib.util.spec_from_file_location(unique_mod_name, os.path.join(mod_dir, file_name)) if spec and spec.loader: mod_module = importlib.util.module_from_spec(spec) spec.loader.exec_module(mod_module) # 筛选出IceMod的子类并实例化 for attr_name in dir(mod_module): attr = getattr(mod_module, attr_name) if isinstance(attr, type) and issubclass(attr, iml_lib.IceMod) and attr != iml_lib.IceMod: mods.append(attr()) # 实例化时会执行__init__加载专属模块 return mods
这个方案的好处是完全不会污染全局的sys.path和sys.modules,每个Mod的模块都是独立的,更适合长期维护。
额外提醒
- 不要在Mod类的类变量(比如
modifications = [cats.CustomCat()])里直接引用其他模块的类,因为类定义时就会执行导入,这时候如果路径还没处理好,就会出错。方案2里改成在__init__里初始化,就是为了延迟到加载Mod实例时才导入,这时候路径已经处理好了。 - 如果你用方案1,一定要确保在
finally块里恢复sys.path和清理缓存,哪怕导入过程中出错了,也不会影响后续Mod的加载。
按照这个思路改完,你再运行加载器,两个Mod的cats.py就会被当成独立的模块,不会再出现找不到类的错误了!




