如何从文件夹动态加载所有模块?加载时遇ModuleNotFoundError报错
我看到你在尝试动态加载plugins目录下的模块时遇到了ModuleNotFoundError,问题出在__init__.py里无法导入first模块。这是因为手动用importlib加载模块时,Python的模块搜索路径默认不包含你的plugins目录,而且动态加载的模块没有被正确识别为包的一部分。下面给你几种可行的解决方案:
方法1:将plugins目录添加到Python的模块搜索路径
最简单的方式是把plugins的绝对路径加入sys.path,这样Python就能找到该目录下的所有模块了。修改你的loader.py:
import importlib.util import os import sys # 新增导入sys模块 search_dir = "./plugins" plugins_module = [] def loader(): path = os.path.realpath(search_dir) # 将plugins目录添加到sys.path if path not in sys.path: sys.path.append(path) for module in os.listdir(path): # 跳过__pycache__等非py文件 if not module.endswith(".py") or module == "__pycache__": continue spec = importlib.util.spec_from_file_location(module.split(".")[0], os.path.join(path,module)) plugin_module = importlib.util.module_from_spec(spec) spec.loader.exec_module(plugin_module) plugins_module.append(plugin_module) loader()
这样修改后,__init__.py里的from first import hello就能正常找到first模块了。
方法2:在__init__.py中使用相对导入
另一种方式是利用Python的相对导入语法,让__init__.py从当前包内导入first模块。修改plugins/__init__.py:
from .first import hello # 加上点号表示相对当前包导入 def main(): print("hello from __init__.py") hello()
不过这种方式需要确保动态加载时,plugins目录被识别为一个包。你可以在加载__init__.py时,给模块设置__package__属性,修改loader.py里的加载逻辑:
import importlib.util import os search_dir = "./plugins" plugins_module = [] def loader(): path = os.path.realpath(search_dir) for module in os.listdir(path): if not module.endswith(".py") or module == "__pycache__": continue module_name = module.split(".")[0] spec = importlib.util.spec_from_file_location(module_name, os.path.join(path,module)) plugin_module = importlib.util.module_from_spec(spec) # 如果是__init__.py,设置__package__属性为plugins if module == "__init__.py": plugin_module.__package__ = "plugins" spec.loader.exec_module(plugin_module) plugins_module.append(plugin_module) loader()
这种方式更符合Python的包结构规范,不会污染全局的sys.path。
方法3:使用importlib.import_module(更简洁的动态加载方式)
如果你不需要手动处理spec的细节,可以用importlib.import_module来加载模块,前提是先把plugins目录的父目录添加到sys.path,并把plugins作为包来导入:
修改loader.py:
import importlib import os import sys search_dir = "./plugins" plugins_module = [] def loader(): # 获取plugins目录的父目录路径 parent_dir = os.path.dirname(os.path.realpath(search_dir)) if parent_dir not in sys.path: sys.path.append(parent_dir) # 先导入plugins包 plugins_package = importlib.import_module("plugins") plugins_module.append(plugins_package) # 加载plugins下的其他模块(比如first.py) for module in os.listdir(search_dir): if module.endswith(".py") and module != "__init__.py" and module != "__pycache__": module_name = f"plugins.{module.split('.')[0]}" plugin_module = importlib.import_module(module_name) plugins_module.append(plugin_module) loader()
这种方式更贴近Python原生的导入逻辑,代码也更简洁,同时能自然处理包内的相对导入。
你可以根据自己的需求选择其中一种方法,个人推荐方法2或3,它们更符合Python的模块包设计理念。
内容的提问来源于stack exchange,提问作者vishal choudhary

