动态导入指定全路径模块的方法

动态导入指定全路径模块的方法

技术背景

在Python开发中,有时需要根据给定的全路径动态导入模块。这种需求可能出现在需要动态加载配置文件、插件化开发等场景中。Python不同版本提供了不同的方式来实现动态导入模块,下面将详细介绍。

实现步骤

Python 3.5+

1
2
3
4
5
6
7
import importlib.util
import sys
spec = importlib.util.spec_from_file_location("module.name", "/path/to/file.py")
foo = importlib.util.module_from_spec(spec)
sys.modules["module.name"] = foo
spec.loader.exec_module(foo)
foo.MyClass()

Python 3.3 和 3.4

1
2
3
4
from importlib.machinery import SourceFileLoader

foo = SourceFileLoader("module.name", "/path/to/file.py").load_module()
foo.MyClass()

此方法在Python 3.4中已被弃用。

Python 2

1
2
3
4
import imp

foo = imp.load_source('module.name', '/path/to/file.py')
foo.MyClass()

临时添加路径

1
2
3
import sys
sys.path.append("/path/to/my/modules/")
import my_module

永久添加路径

在Linux的.bashrc文件中添加以下行,并在终端执行source ~/.bashrc

1
export PYTHONPATH="${PYTHONPATH}:/path/to/my/modules/"

处理顶级模块为目录的情况(Python 3.5+)

1
2
3
4
5
6
7
8
MODULE_PATH = "/path/to/your/module/__init__.py"
MODULE_NAME = "mymodule"
import importlib
import sys
spec = importlib.util.spec_from_file_location(MODULE_NAME, MODULE_PATH)
module = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = module
spec.loader.exec_module(module)

使用runpy.run_path

1
2
from runpy import run_path
settings = run_path("/path/to/file.py")

使用__import__chdir

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def import_file(full_path_to_module):
try:
import os
module_dir, module_file = os.path.split(full_path_to_module)
module_name, module_ext = os.path.splitext(module_file)
save_cwd = os.getcwd()
os.chdir(module_dir)
module_obj = __import__(module_name)
module_obj.__file__ = full_path_to_module
globals()[module_name] = module_obj
os.chdir(save_cwd)
except Exception as e:
raise ImportError(e)
return module_obj


import_file('/home/somebody/somemodule.py')

使用pkgutil模块

1
2
3
4
5
6
7
import pkgutil
import importlib

packages = pkgutil.walk_packages(path='.')
for importer, name, is_package in packages:
mod = importlib.import_module(name)
# do whatever you want with module now, it's been imported!

使用thesmuggler

1
2
3
4
5
6
7
8
9
10
from thesmuggler import smuggle

# à la `import weapons`
weapons = smuggle('weapons.py')

# à la `from contraband import drugs, alcohol`
drugs, alcohol = smuggle('drugs', 'alcohol', source='contraband.py')

# à la `from contraband import drugs as dope, alcohol as booze`
dope, booze = smuggle('drugs', 'alcohol', source='contraband.py')

兼容所有Python版本的方法

1
2
3
4
config_file = "/tmp/config.py"
with open(config_file) as f:
code = compile(f.read(), config_file, 'exec')
exec(code, globals(), locals())

使用importlib的简单方法

1
2
3
4
5
6
7
8
9
10
11
import importlib
import os
import sys

dirname, basename = os.path.split(pyfilepath) # pyfilepath: '/my/path/mymodule.py'
sys.path.append(dirname) # only directories should be added to PYTHONPATH
module_name = os.path.splitext(basename)[0] # '/my/path/mymodule.py' --> 'mymodule'
module = importlib.import_module(module_name) # name space of defined module (otherwise we would literally look for "module_name")

a = module.myvar
b = module.myfunc(a)

2024年解决方案

1
2
3
4
5
6
7
8
9
10
11
import importlib
import importlib.machinery
import importlib.util
import sys

pkg = "mypkg"
spec = importlib.machinery.PathFinder().find_spec(pkg, ["/path/to/mypkg-parent"])
mod = importlib.util.module_from_spec(spec)
sys.modules[pkg] = mod # needed for exec_module to work
spec.loader.exec_module(mod)
sys.modules[pkg] = importlib.import_module(pkg)

使用符号链接(Linux)

1
ln -s /absolute/path/to/module/module.py /absolute/path/to/script/module.py

在Python脚本中:

1
from module import *

加载编译的Python模块(3.4)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import sys
import importlib.machinery

def load_module(name, filename):
# If the Loader finds the module name in this list it will use
# module_name.__file__ instead so we need to delete it here
if name in sys.modules:
del sys.modules[name]
loader = importlib.machinery.ExtensionFileLoader(name, filename)
module = loader.load_module()
locals()[name] = module
globals()[name] = module

load_module('something', r'C:\Path\To\something.pyd')
something.do_something()

使用importmonkey工具

1
2
3
4
from importmonkey import add_path
add_path("../relative/path") # relative to current __file__
add_path("/my/absolute/path/to/somewhere") # absolute path
import project

使用pathlib的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from pathlib import Path
from importlib.util import spec_from_file_location, module_from_spec
from typing import Optional


def get_module_from_path(path: Path, relative_to: Optional[Path] = None):
if not relative_to:
relative_to = Path.cwd()

abs_path = path.absolute()
relative_path = abs_path.relative_to(relative_to.absolute())
if relative_path.name == "__init__.py":
relative_path = relative_path.parent
module_name = ".".join(relative_path.with_suffix("").parts)
mod = module_from_spec(spec_from_file_location(module_name, path))
return mod


def get_modules_from_folder(folder: Optional[Path] = None, glob_str: str = "*/**/*.py"):
if not folder:
folder = Path(".")

mod_list = []
for file_path in sorted(folder.glob(glob_str)):
mod_list.append(get_module_from_path(file_path))

return mod_list

最佳实践

  • 优先使用Python官方推荐的方法,如Python 3.5+使用importlib.util
  • 对于临时导入模块,使用sys.path.append;对于长期使用的模块,建议永久添加路径。
  • 在处理不同版本兼容性时,尽量编写兼容代码或进行版本判断。

常见问题

  • SystemError: Parent module 'mymodule' not loaded, cannot perform relative import:在Python 3.5+处理顶级模块为目录时,需要在执行exec_module前将模块名绑定到sys.modules中。
  • 模块已存在于sys.modules:在加载模块前,可以先检查并删除sys.modules中的对应模块,避免出现问题。

动态导入指定全路径模块的方法
https://119291.xyz/posts/dynamically-import-module-by-full-path/
作者
ww
发布于
2025年5月26日
许可协议