• 从指定路径加载 Python 模块


    快速说明

    1. 使用 sys.path

    import sys
    sys.path.insert(0, 'c:/workspace')
    #   假设该目录下有一个 hello_world 文件夹.
    
    from hello_world import say_hello
    say_hello()  # -> 'hello world!'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2. 从 zip 文件加载

    import sys
    sys.path.append('c:/workspace/hello_world.zip')
    
    from hello_world import say_hello
    say_hello()  # -> 'hello world!'
    
    • 1
    • 2
    • 3
    • 4
    • 5

    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
    • 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
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    详细说明

    1. 使用 sys.path

    python 在查找模块时, 会从 sys.path (类型为 list) 中寻找.

    例如, 当我们 import hello_world, 那么就要保证 hello_world 的实际路径的 父目录 必须存在于 sys.path 中, 否则就会报 ModuleNotFoundError.

    默认情况下, 我们从命令行启动 python, sys.path 的初始值是 (以 windows 为例):

    [
        '/python310.zip',
        '/DLLs',
        '',
        '/lib/site-packages',
    ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    注意不同系统打印的结果存在一些差异. 使用 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!'
    
    • 1
    • 2
    • 3
    • 4
    • 5

    使用方法非常直观易懂. 而且不会带来类似 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
    
    
    • 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
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
  • 相关阅读:
    代码随想录-day1
    代码大全阅读随笔(五)
    戏说c语言: 阶段性总结二
    经典文献阅读之--BoW3D
    MVVM 与 MVC区别和应用场景?
    Day17-购物车页面-收货地址-把address信息存储到vuex中
    【华为机试真题 Python实现】动态规划-如何写一个递归函数
    2023.11.17使用flask将多个图片文件上传至服务器
    会计学原理知识点总结
    参加Ultimate Harvest Moon活动,立即赢取终极版月光女神NFT
  • 原文地址:https://blog.csdn.net/Likianta/article/details/126660058