关于importlib 动态加载的使用

项目中有关于对Python包动态加载的需求,而且要求是不局限于当前目录下的全局导包,而且要能支持包更新,源程序同步更新

这里使用到了importlib这个包,先放出工具类

import importlib
import sys
import os


class NameConflictError(Exception):
    def __init__(self, error_msg: str) -> None:
        super().__init__(self)
        self.error_msg = error_msg

    def __str__(self) -> str:
        return self.error_msg


class ModuleLoader(object):
    @staticmethod
    def get_module(module_name: str, package_path=None):
        if package_path is None:
            package_path = os.getcwd()
        # Ensure wanted module import from wanted pkg
        if package_path in sys.path:
            sys.path.remove(package_path)
        sys.path.insert(0, package_path)
        mod = importlib.import_module(module_name)
        exist_module_pkg_path = os.path.dirname(mod.__file__)
        if package_path != exist_module_pkg_path:
            mod = importlib.reload(mod)
            # Read again
            exist_module_pkg_path = os.path.dirname(mod.__file__)
        if package_path != exist_module_pkg_path:
            raise NameConflictError(
                f"The module {package_path} not exist or priority lower than {exist_module_pkg_path}")
        else:
            return mod

    @staticmethod
    def get_member(module_name: str, func_name: str, package_path=None):
        return getattr(ModuleLoader.get_module(module_name, package_path), func_name)

其实这边有几种导出场景是需要注意的,场景如下

前提A包为当前读取包,B包为历史已读入过的包 F为所需读取的函数(✅为相同 ❌为不同)

Pkg路径 Module路径 函数名 结果
A包有则成功,否则报错
A包有则成功,否则报错
假如A包有F函数则读A包的F函数,否则读B包的F函数,均无则报错
❌or ✅ A包有则成功,否则报错

其实总结下来,python内部维护了一个关于module独立的对象,当读入多个不同的同名module时,python只会更新修改过的函数或者添加新的函数,而不会对被删除的函数进行去除。

例如:

# test.A
def add():
  return 100

def del():
  return 500

# --------上下不同包--------

# try.A
def add():
  return 200

上述情况假如先加载test.A,执行add,再加载try.A,执行add, 此时的输出是100, 200, 但假如此时你再执行del, 因为try.A是后读入的,其本身是没有del函数的,但是因为历史读入的test.A有,因此输出结果为500

还有一个点就是只有reload才会重新加载包,比如先读入了test.A,要想读入try.A的话就必须reload,否则还会是test.A

KAI Python, 编程语言