Python的import详解与模块自动加载

import与sys.path

模块import导入顺序

  • 在 sys.modules 中查找,它缓存了所有已导入的模块, 这里默认在运行前会自动加载内置库. 此变量中包括了解释器的内置库,即在sys.builtin_module_names中搜索,返回第一个找到的结果

  • 在 sys.meta_path 中查找,它支持自定义的加载器

  • 按顺序在sys.path中搜索,排在前面的优先被找到,返回第一个找到的结果

  • 若未找到,抛出ImportError异常

  • 注意:导入不同模块的相同函数时,需要注意文件的命名规范以及函数作用域等等,除函数重载外尽量避免使用相同的命名,否则后引入的会覆盖先引入的

IDE与命令行差异

首先在解决这个问题之前, 需要了解一个问题.

使用Pycharm时,我们在编辑器中执行,当前的工作目录为你的项目根目录/文件目录
命令行中运行Python文件时,当前工作目录为你运行的Python文件所在的目录

# Pycharm中执行print(sys.path)时返回,注意了py文件和项目根目录加入到了工作目录

['E:\\www\\CpsWarehouse\\common', 'E:\\www\\CpsWarehouse', 'D:\\Program Files\\JetBrains\\PyCharm 2022.2.3\\plugins\\python\\helpers\\pycharm_display', 'D:\\Anaconda3\\envs\\CpsWarehouse\\python39.zip', 'D:\\Anaconda3\\envs\\CpsWarehouse\\DLLs', 'D:\\Anaconda3\\envs\\CpsWarehouse\\lib', 'D:\\Anaconda3\\envs\\CpsWarehouse', 'D:\\Anaconda3\\envs\\CpsWarehouse\\lib\\site-packages', 'D:\\Program Files\\JetBrains\\PyCharm 2022.2.3\\plugins\\python\\helpers\\pycharm_matplotlib_backend']

# 命令行中执行print(sys.path)时返回

['E:\\www\\CpsWarehouse\\common', 'D:\\Anaconda3\\envs\\CpsWarehouse\\python39.zip', 'D:\\Anaconda3\\envs\\CpsWarehouse\\DLLs', 'D:\\Anaconda3\\envs\\CpsWarehouse\\lib', 'D:\\Anaconda3\\envs\\CpsWarehouse', 'D:\\Anaconda3\\envs\\CpsWarehouse\\lib\\site-packages']

我们发下如下特点

  • sys.path中的第一个值是被执行的py文件所在文件夹在操作系统中的绝对路径
  • 命令行中如果想导入E:\\www\\CpsWarehouse其他包,是不成功的,此目录不在sys.path中,也不能使用..等相对路径来调用, 因为没有已知父包的情况下无法访问上级目录
  • 通常我们可以在一个大项目中,将项目根目录加入到sys.path中,之后使用根目录路径导入会比较方便
  • sys.modules全局字典(具有字典的所有功能),每当导入新模块时,其都将记录这些模块.可以看成一个导入缓冲,第二次导入同模块时,可以直接从缓冲中查找从而加快速度
# 正确的修改sys.path
# 项目文件在 /you_path/common/init.py
import os
import sys
# 父目录 /you_path/ 
parent_path = os.path.dirname(os.path.dirname(__file__))
# 判断项目根目录是否在sys.path中, 不存在就添加
if parent_path not in sys.path:
    sys.path.append(parent_path)

Python动态导入

#import imp
import importlib

# 系统函数__import__() 
def test_import():
    module_name = "class_define"
    my_module = __import__(module_name)
    print("test_import imported module: ", my_module)
    print("test_import module: ", dir(my_module))

    class_name = "MyClass"
    class_ = getattr(my_module, class_name)
    print("test_import get class: ", class_)

    obj = class_()
    #for attr in dir(obj):
    print("test_import obj attr: ", dir(obj))

# 系统函数__import__()  
def test_import_class():
    module_name = "class_define"
    class_name = "MyClass"
    my_module = __import__(module_name, globals(), locals(), fromlist=[class_name])
    print("test_import_class imported module: ", my_module)
    print("test_import_class module: ", dir(my_module))

# importlib模块(官方推荐)
def test_importlib():
    module_name = "class_define"
    class_name = "MyClass"
    my_module = importlib.import_module(".", module_name)
    print("test_importlib imported module: ", my_module)
    print("test_importlib module: ", dir(my_module))

    importlib.reload(my_module)

# exec
def test_exec():
    lo = locals()
    module_name = "class_define"
    class_name = "MyClass"
    import_str = "import {}".format(module_name)

    my_module = exec(import_str)
    print("test_exec imported module: ", lo[module_name])
    print("test_exec module: ", dir(lo[module_name]))

if __name__ == "__main__":
    test_import()
    test_import_class()
    test_importlib()
    test_exec()

自动导入指定文件夹中的所有 Python 文件


附录

python中import 模块的路径问题
聊聊Python模块导入机制与大型项目规范

此处评论已关闭