You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

同名不同路径模块的动态加载冲突解决:避免不同Mod目录下的cats.py被识别为同一模块

同名不同路径模块的动态加载冲突解决:避免不同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_classesload_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类,延迟加载专属模块

modificationsscripts改成在__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.pathsys.modules,每个Mod的模块都是独立的,更适合长期维护。

额外提醒

  • 不要在Mod类的类变量(比如modifications = [cats.CustomCat()])里直接引用其他模块的类,因为类定义时就会执行导入,这时候如果路径还没处理好,就会出错。方案2里改成在__init__里初始化,就是为了延迟到加载Mod实例时才导入,这时候路径已经处理好了。
  • 如果你用方案1,一定要确保在finally块里恢复sys.path和清理缓存,哪怕导入过程中出错了,也不会影响后续Mod的加载。

按照这个思路改完,你再运行加载器,两个Mod的cats.py就会被当成独立的模块,不会再出现找不到类的错误了!

火山引擎 最新活动