快速说明
1. 使用 sys.path
import sys
sys.path.insert(0, 'c:/workspace')
# 假设该目录下有一个 hello_world 文件夹.
from hello_world import say_hello
say_hello() # -> 'hello world!'
2. 从 zip 文件加载
import sys
sys.path.append('c:/workspace/hello_world.zip')
from hello_world import say_hello
say_hello() # -> 'hello world!'
3. 使用 python 标准库 importlib 加载特定路径
import sys
from importlib import util
from os.path import basename
from os.path import exists
from types import ModuleType
def load_package_from_path(pkg_path: str) -> ModuleType:
"""
ref: https://stackoverflow.com/a/50395128
"""
init_path = f'{pkg_path}/__init__.py'
assert exists(init_path)
name = basename(pkg_path)
spec = util.spec_from_file_location(name, init_path)
module = util.module_from_spec(spec)
sys.modules[spec.name] = module
spec.loader.exec_module(module)
return module
if __name__ == '__main__': # test
mod_a = load_package_from_path(
'c:/workspace/project_1/hello_world'
)
mod_b = load_package_from_path(
'c:/workspace/project_2/hello_world'
)
mod_a.say_hello() # -> 'hello world from alpha!'
mod_b.say_hello() # -> 'hello world from beta!'
详细说明
1. 使用 sys.path
python 在查找模块时, 会从 sys.path (类型为 list) 中寻找.
例如, 当我们 import hello_world
, 那么就要保证 hello_world 的实际路径的 父目录 必须存在于 sys.path 中, 否则就会报 ModuleNotFoundError.
默认情况下, 我们从命令行启动 python, sys.path 的初始值是 (以 windows 为例):
[
'<python 安装目录>/python310.zip',
'<python 安装目录>/DLLs',
'<python 安装目录>',
'<python 安装目录>/lib/site-packages',
]
注意不同系统打印的结果存在一些差异. 使用 IDE 启动, 也会和命令行启动存在一些差异.
sys.path 查找模块的优先级 (从高到低):
- 环境变量 PYTHONPATH (如果有的话)
- 靠前的路径
- 靠后的路径
该优先级决定了, 如果不同目录下有同名的子文件夹, 那么靠前的目录下的才会被成功导入.
缺点
- 细粒度以目标模块的父目录来区分, 如果父目录下有其他文件夹, 会给导入空间带来隐患
- 一旦导入成功, 之后即使在 sys.path 中修改了目录, 也不会再次查找
2. 从 zip 文件加载
直接在 sys.path 中添加该 zip 文件的路径即可, 可以是绝对路径, 也可以相对路径.
import sys
sys.path.append('c:/workspace/hello_world.zip')
from hello_world import say_hello
say_hello() # -> 'hello world!'
使用方法非常直观易懂. 而且不会带来类似 sys.path 的方法中那种导入空间被污染的隐患.
缺点
- 需要先将目标打包为 zip 文件
- 加载速度会受到影响
- zip 文件内的 py 文件的相对路径问题
- 适用场景有限
3. 使用 python 标准库 importlib 加载特定路径
参考 SO 上的一个回答, 我个人觉得此方案非常优秀. 它有效解决了同名但不同目录的包的导入, 导入空间可能被污染的隐患, 以及导入效率等问题.
你可以将下面封装好的函数另存为一个 py 文件, 方便以后使用:
"""
filename: pyimport.py
usage:
from pyimport import load
mod_a = load('c:/workspace/project_1/hello_world')
mod_b = load('c:/workspace/project_2/hello_world')
mod_a.say_hello() # 'hello world from alpha!'
mod_b.say_hello() # 'hello world from beta!'
"""
import sys
from importlib import util
from os.path import basename
from os.path import exists
from types import ModuleType
def load_package_from_path(pkg_path: str) -> ModuleType:
"""
ref: https://stackoverflow.com/a/50395128
"""
init_path = f'{pkg_path}/__init__.py'
assert exists(init_path)
name = basename(pkg_path)
spec = util.spec_from_file_location(name, init_path)
module = util.module_from_spec(spec)
sys.modules[spec.name] = module
spec.loader.exec_module(module)
return module
load = load_package_from_path # short alias