python动态加载插件

获取当前脚本路径

根据当前位置获取绝对路径,区分打包后的可执行文件开发环境

def get_plugins_dir(relative_path: str = "plugins") -> str:
    """
    获取应用程序的插件目录
    如果是打包后的exe,返回exe所在目录
    如果是脚本,返回脚本所在目录
    """
    if getattr(sys, "frozen", False):
        # 打包后的exe
        pos = os.path.dirname(sys.executable)
    else:
        # 开发环境
        pos = os.path.dirname(os.path.abspath(__file__))
    pos = os.path.join(pos, relative_path)

    if not os.path.exists(pos):
        os.makedirs(pos)
        # 可选添加示例插件

    return pos

加载环境变量

使用闭包,允许热加载功能

def make_env_manager():
    """
    设置动态加载包的环境变量
    """
    env_path = []

    def inner(full_path: str):
        nonlocal env_path
        # 清空
        for path in list(env_path):
            sys.path.remove(path)
        # 记录环境路径
        env_path.append(os.path.dirname(full_path))
        for root, dirs, files in os.walk(full_path):
            if root not in sys.path:
                env_path.append(root)
        # 添加
        for path in env_path:
            sys.path.insert(0, path)

    return inner

加载插件

区分加载单个文件

def load_file(file_path: str, validate_func: Optional[Callable] = None):
    """
    加载单个插件文件
    """

    def validate_plugin(module):
        if not hasattr(module, "execute"):
            sys.modules.pop(module)
            raise ImportError(f"插件 {name} 缺少 execute() 函数")
        return True

    if validate_func is None:
        validate_func = validate_plugin

    try:
        name = os.path.splitext(os.path.basename(file_path))[0]

        spec = importlib.util.spec_from_file_location(name, file_path)
        if spec is None:
            raise ImportError(f"无法从文件创建模块规范: {file_path}")

        module = importlib.util.module_from_spec(spec)
        sys.modules[name] = module
        spec.loader.exec_module(module)

        return module if validate_func(module) else None
    except Exception as e:
        # log error
        traceback.print_exc()
        return None


def load_package(package_path: str, validate_func: Optional[Callable] = None):
    """
    加载单个插件包
    """

    def validate_plugin(module):
        if not hasattr(module, "execute"):
            sys.modules.pop(module)
            raise ImportError(f"插件 {name} 缺少 execute() 函数")
        return True

    if validate_func is None:
        validate_func = validate_plugin

    try:
        name = os.path.basename(package_path)

        if package_path not in sys.path:
            sys.path.insert(0, package_path)

        module = importlib.import_module(name)
        if validate_func(module):
            return module

        try:
            main_module = importlib.import_module(f"{name}.main")
            return main_module if validate_func(main_module) else None
        except ImportError:
            raise ImportError(f"插件包 {name} 中未找到可用的主模块")
    except Exception as e:
        traceback.print_exc()
        return None

获取所有插件

根据传入路经筛选插件

def scan_plugins(path: str) -> list[str]:
    file_ans = list(
        filter(
            lambda x: x != "",
            map(
                lambda name: (
                    os.path.join(path, name)
                    if not name.startswith(".")
                    and not name.startswith("__")
                    and os.path.splitext(name)[1] in [".py", ""]
                    else ""
                ),
                os.listdir(path),
            ),
        )
    )
    return file_ans

Example

  1. 获取插件文件夹路径

  2. 获取所有插件

  3. 加载环境变量

  4. 加载插件

if __name__ == "__main__":
    base_path = get_plugins_dir()
    p = scan_plugins(base_path)

    env_manager = make_env_manager()
    env_manager(base_path)

    loaded_plugins = load_plugins(p)
    print(loaded_plugins)

示例文件下载

posted @ 2025-10-29 14:59  ling-yuan  阅读(11)  评论(0)    收藏  举报