AI大模型入门-模块导入import

视频链接:

不如语冰:

https://space.bilibili.com/70431433?spm_id_from=333.1007.0.0

文章代码链接:

GitHub - zyf-ngu/Qmatter: 跟着问题学-AI大模型入门系列笔记和代码 · GitHub

模块 类和 函数) 的导入

实际的项目中不可能一份代码文件实现所有的功能,因此需要借助外在的力量,这份力量主要有2类,一种是系统库集成的,像numpy处理数据,另一种是自己项目写的函数和类,但是放到不同的位置。

当前的代码怎么使用这些外在的力量(类和函数)呢?

方法就是import导入,将其它外在的类和函数导入到当前运行的代码文件里,下面详细学习一下。

基本原理

首先明确几个概念,项目中的文件夹对应Python的包/库package,文件对应模块module,模块的名称就是Python代码文件取消扩展名.py

包package和模块module的导入核心就是找到模块并完成导入,问题来了,如何找到模块呢?找不到模块也是经常导入报错的原因,答案就是在设置的路径里寻找。具体来说,寻找导入模块的步骤如下:

1.在sys.modules缓存中查找是否已经导入过该模块,如果存在则直接使用。这一步通常不进行人为的操作;

2.如果不在缓存中,则搜索模块文件,这部分是需要重点关注的,排查导入报错问题也是需要先看这块。搜索路径由sys.path定义,一般包括当前目录、环境变量PYTHONPATH指定的目录以及Python安装目录(这个一般是conda或pip安装指定的目录)。可以手动添加路径。

3.除了直接在路径里寻找之外,还经常使用的是相对路径导入,这个后面会详细介绍分析。

找到模块文件之后,就是将模块的代码整合到运行的代码里,具体如下:

  1. 找到模块文件后,将其编译为字节码(如果必要),然后执行模块中的代码(注意:模块顶层的import代码在导入时会被执行,下面的代码看是否有if name==main)。

2.将模块对象添加到`sys.modules`缓存中,并创建到当前命名空间的引用(根据导入方式)。

导入分类

从导入内容和数量来看,

可以直接导入整个模块,

import module_name;

使用的模块中的内容 :

module_name.function_name或 module_name.Class_name

可以导入模块的部分函数 from module_name import function_0 , function_1 ,此时直接使用函数名或类名,无需模块名前缀

也可以 导入模块的全部函数

from module_name import *

因为有的模块和函数名字比较长,或者要导入的函数的名称可能与程序中现有的名称冲突,可以在导入的时候使用as起个别名

import module_name as new_name

从导入方式来看,

分为相对导入和绝对导入,其区别为是否写出模块的绝对路径。

项目根目录与顶级包

下面我们重点区分一下项目根目录和顶级包这个概念,为学习后面的相对导入和绝对导入做好准备。

项目根目录(project_root,项目名)本身不是一个Python包,因为它没有__init__.py,但当我们将项目根目录添加到sys.path后,就可以直接导入其下面的子文件夹作为包package,找不到主目录时,可以手动设置:

# 设置项目根目录

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(file))))

# 在main文件里 添加项目根目录到 sys.path

sys.path.append(os.path.dirname(os.path.abspath(file)))

注意一般情况下项目名称不是顶级包,下面的子文件夹才是顶级包。

项目根目录 vs 顶级包 关键区别

|-------------|--------------------|-------------------------|
| 特性 | 项目根目录 | 顶级包 |
| 定义 | 包含整个项目的文件夹 | 项目中包含 init.py 的顶级目录 |
| Python 识别 | 不在 sys.path 中时不被识别 | Python 将其视为可导入的包 |
| init.py | 不需要 | 必须存在 |
| 导入方式 | 不可直接导入 | 可直接导入 |
| 典型位置 | 包含多个包的父目录 | 项目根目录下的一级子文件夹 |

绝对导入就是从顶级包开始,每个子包都写上

import package1.package2... .module1

相对导入就是确定要导入的模块和当前运行模块的相对位置关系,相对导入本质上也是转换成绝对导入,其连接点就是当前运行模块所归属的__package__属性。根据这个属性,Python会得到当前运行模块所属的包,然后根据前面有几个点(.)来推导出要导入模块的父包

from . import module → from my_package import module

以下面的实例详细解释一下点号含义和相对导入的使用方法:

project_root/ <-- 项目根目录

├── main.py

├── core/ <-- 一级子包

│ ├── init.py

│ ├── utils.py

│ └── services/ <-- 二级子包

│ ├── init.py

│ ├── api.py

│ └── auth.py

└── lib/ <-- 一级子包(同级目录)

├── init.py

├── network.py

└── storage/

├── init.py

└── cloud.py

点号含义

. 表示当前文件所在的目录(包),.. 表示上一级目录(父包),... 表示祖父包(但不推荐使用),例如

在 core/services/api.py 中:. = core.services,.. = core

在 lib/network.py 中:. = lib,.. = lib

在 core/utils.py 中:. = core,.. = 项目根目录(无父包)

以上面目录结构为例,相对导入可以导入当前包下的模块,子包下的模块(子包.module),具体到代码里:

假设我们在 core/services/api.py 中:

✅ 正确用法

from . import auth # 导入同级的 auth.py

from .. import utils # 导入父级的 utils.py

❌ 错误用法

from ... import main # 错误!超过两级父目录

from ..lib import network # 错误!不能跨兄弟目录

也就是说,相对导入一般最多只导入父包下的模块,和父包同级的,父包上级的模块都不能导入。

相对导入的问题

因为相对导入也是要转换为绝对导入,而且是依赖__package__属性的,就会出现一个相对导入最常出现的问题,找不到包。

相对导入只能在package里面的module使用,并且使用了相对导入的模块必须由其它模块调用而不能直接运行。

当直接运行包内的模块文件时:

python my_package/submodule.py

会发生下面的问题:

Python 将该文件视为顶级脚本 类似于main.py 而非包的一部分,package 被设置为 None,不知道其属于哪个package,则相对导入无法确定父包位置,也就找不到其它模块,导入失败并抛出 ImportError

解决方案有下面几种:

方案 1:使用外部入口点(推荐),比如在main.py主文件里调用运行

# 项目根目录 /main.py

import sys

import os

# 导入包内的模块

from my_package import submodule

if name == "main":

submodule.run()

运行:python main.py

方案 2:使用 -m 参数(模块运行模式)

# 在 项目根目录 运行

python -m my_package.submodule

注意是module,不是.py,也就是去掉.py的模块名。

这样 Python 会:识别完整的包路径 my_package.submodule并正确设置 package = "my_package"启用相对导入

方案 3:动态修复包上下文(高级技巧)

# submodule.py

顶部添加if name == "main" and package is None:

# 手动设置包上下文

import os

import sys

# 计算包路径 (my_package.submodule → my_package)

file_path = os.path.abspath(file)

base_dir = os.path.dirname(os.path.dirname(file_path))

package_name = os.path.basename(base_dir)

# 添加到 sys.path

if base_dir not in sys.path:

sys.path.insert(0, base_dir)

# 设置包上下文

package = package_name

# 重新导入当前模块(确保相对导入生效)

import importlib

importlib.import_module(name)

注意事项:

  1. **循环导入**:两个模块相互导入可能导致问题,应尽量避免。可以通过重构代码(如将导入语句放在函数内部或使用延迟导入)来解决。

  2. **重复导入**:多次导入同一个模块,实际上只会执行一次(由于`sys.modules`缓存)。但可以多次使用`import`语句,不过通常只导入一次。

  3. **模块执行**:导入模块时,模块顶层的代码(包括print语句、函数定义等)会被执行。因此,通常将可执行代码封装在`if name == 'main':`中,避免在导入时执行。

  4. **命名冲突**:使用`from ... import *`可能导致当前命名空间中已有的名字被覆盖。推荐使用导入整个模块并起别名的方式,或者明确导入需要的名字。

动态导入

在代码的实际运行中,我们会遇到根据参数导入特定的模块,这时可以使用动态导入模块。importlib.import_module提供了这种动态导入的能力。import_module函数内部调用了`import()`函数,它返回导入的模块对象,我们可以通过该对象访问模块中的内容。

importlib.import_module(package名或者模块名)

def get_loader(loader_name: str, file_path: str, loader_kwargs: Dict = None):

'''

根据loader_name和文件路径或内容返回文档加载器。

'''

loader_kwargs = loader_kwargs or {}

try:

if loader_name in "RapidOCRPDFLoader", "RapidOCRLoader":

document_loaders_module = importlib.import_module('document_loaders')

else:

document_loaders_module = importlib.import_module('langchain.document_loaders')

DocumentLoader = getattr(document_loaders_module, loader_name)

except Exception as e:

msg = f"为文件{file_path}查找加载器{loader_name}时出错:{e}"

logger.error(f'{e.class.name}: {msg}',

exc_info=e if log_verbose else None)

document_loaders_module = importlib.import_module('langchain.document_loaders')

DocumentLoader = getattr(document_loaders_module, "UnstructuredFileLoader")

关键要点:

动态导入虽然强大,但应谨慎使用。在大多数场景下,静态导入仍然是首选,因为它更易于理解和调试。动态导入最适合需要运行时灵活性的特定场景。

优先使用 importlib.import_module() 而非 import()

始终验证动态导入的输入来源

合理利用缓存避免重复导入开销

为动态导入提供清晰的错误处理

理解 Python 导入机制底层原理

相关推荐
岑梓铭7 小时前
《考研408数据结构》第七章(7.1 查找:顺序查找、折半查找、分块查找)复习笔记
数据结构·笔记·考研·408·ds·查找
海兰7 小时前
【高速缓存】RedisVL 指南:从向量搜索到 AI 应用落地
人工智能·redis
智恒百亿7 小时前
RTX 5090服务器全场景技术解析:极致算力的行业落地与价值赋能
大数据·人工智能·5090服务器
投票竞赛7 小时前
书法、绘画作品投票评选,图片投票小程序作品集排版
python·小程序
金伟API10247 小时前
常见的SQL面试题:经典50例
数据库·人工智能·笔记·sql·学习
硅徒7 小时前
金融系统渗透测试复盘:从授权边界到报告整改的全流程
人工智能
学术小白人7 小时前
【倒计时4个月】-AI赋能图像处理与计算机视觉技术国际学术研讨会
网络·人工智能·神经网络·数据分析·光学
俊哥V7 小时前
每日 AI 研究简报 · 2026-07-20
人工智能·ai
梦远青城7 小时前
Docker 部署python的paddle进行OCR文字识别身份证
python·docker·ocr·paddle·身份证识别