从指定路径加载 Python 模块

本文详细介绍了Python中三种不同的模块导入方式:1) 使用sys.path添加自定义路径;2) 直接从zip文件加载模块;3) 利用importlib标准库动态加载指定路径的模块。每种方法都有其适用场景和优缺点,例如sys.path可能导致导入空间污染,而importlib提供更灵活且安全的解决方案。
摘要由CSDN通过智能技术生成

快速说明

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 查找模块的优先级 (从高到低):

  1. 环境变量 PYTHONPATH (如果有的话)
  2. 靠前的路径
  3. 靠后的路径

该优先级决定了, 如果不同目录下有同名的子文件夹, 那么靠前的目录下的才会被成功导入.

缺点

  1. 细粒度以目标模块的父目录来区分, 如果父目录下有其他文件夹, 会给导入空间带来隐患
  2. 一旦导入成功, 之后即使在 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 的方法中那种导入空间被污染的隐患.

缺点

  1. 需要先将目标打包为 zip 文件
  2. 加载速度会受到影响
  3. zip 文件内的 py 文件的相对路径问题
  4. 适用场景有限

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值