(Module Resolution / sys.path 搜索机制)
介绍
当你运行:
python a.py
Python 会依次在以下路径中查找模块:
✅ 查找顺序是 sys.path
import sys
print(sys.path)
[
'', # 当前工作目录(或脚本目录)
'/your/project/path', # 执行时添加的路径(如 PYTHONPATH)
'/usr/local/lib/python3.x/site-packages', # 第三方包目录
...
]
✅ 二、模块搜索路径来自哪里?(sys.path是如何被构建的?)
来源 | 说明 |
---|---|
当前工作目录(通常是 .) | 也就是你执行 python script.py 的位置 |
脚本所在目录的父目录(间接推导) | 如果你在 /project/app/services/ 执行了 python a.py,那 /project 会在路径里 |
PYTHONPATH 环境变量 | 你可以自定义模块搜索路径 |
Python 安装目录(stdlib) | 系统级别的模块位置 |
site-packages | 你通过 pip 安装的模块都会在这 |
几种典型启动方式对 sys.path的影响
启动方式 | sys.path[0] 是谁? | 特性 |
---|---|---|
python a.py | 当前脚本所在目录 | ✅ 常用,容易形成隐式路径依赖 |
python -m mypackage.module | 当前工作目录 | ✅ 推荐用于包结构执行 |
import mymodule | 当前 sys.path 查找 | ✅ 由路径列表控制,推荐使用绝对导入 |
❌ 相对导入失败(from .utils import x报错)
# 错误示例:直接运行
python app/module/a.py
报错
ImportError: attempted relative import with no known parent package
✔️ 正确方式应该用:
cd 项目根目录
python -m app.module.a
❌ ModuleNotFoundError: No module named ‘app’
原因:
- sys.path 里没有 app 所在路径
- 通常你是在脚本目录下运行了 python xxx.py
• 设置环境变量:
export PYTHONPATH=/your/project/root
• 或在脚本开头加:
import sys
sys.path.append('/your/project/root')
pycharm 导入路径
✅ 本质原因:PyCharm 的“源目录”(Source Root)机制
🧠 什么是 Source Root?
在 PyCharm 中,如果你右键一个目录 → 选择 Mark Directory as → Sources Root,PyCharm 会:
✅ 自动将这个目录
加入解释器的 sys.path
🟡 所以你的 Python 程序在运行时就能直接 import 这个目录下的包
import sys; print('Python %s on %s' % (sys.version, sys.platform))
sys.path.extend(['app','app/test']
当你运行代码时,PyCharm 背后自动执行了上面代码内容,这让 Python 解释器能找到你代码结构中的模块。
如果你在命令行运行代码(比如 python apps/main.py),解释器的 sys.path 并不会包含 JSAI/ 根目录,除非你:
- 设置了 PYTHONPATH=/Users/edy/Desktop/
- 或者手动在代码中加上:
import sys, os
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
场景 | 建议 |
---|---|
在 PyCharm 中运行 | ✅ 设置 Source Root 非常方便 |
在命令行 / Docker / 服务器中运行 | ✅ 显式配置 PYTHONPATH 或使用 python -m … |
为团队统一路径导入 | ✅ 使用绝对导入 + Source Root 配置一致性 |
pypi 包导入
✅ 答案核心:PyPI 包不是脚本运行的,而是被import的!并不是运行 pip 安装包中的某个模块脚本,而是以“导入包”方式加载它们
import requests
然后它内部文件结构是这样的:
site-packages/
└── requests/
├── __init__.py
├── models.py
└── utils.py
比如在 models.py:
from .utils import some_func # ✅ 相对导入
这是合法的!因为解释器会认为你是以包方式导入的:
import requests.models
而不是直接执行 models.py:
# ❌ 错误示范(你不会这么干):
python /site-packages/requests/models.py
🧠 相对导入只有在“模块是某个包中的成员”时才生效。
🧨 如果你直接运行一个含有相对导入的模块(比如 python some_module.py),
解释器认为它是顶层脚本(__main__),此时相对导入会报错
✅ 为什么 pip 安装的库可以这样用?
因为:
- pip 安装时会把包安装到 site-packages/
- site-packages/ 自动在 sys.path 中
- 当你 import some_package 时,Python 会将它识别为“包结构”,内部的 .py 文件可以安心用相对导入
所以这些库在设计时,是假设你通过 import 包名 来用它的,而不是跑它的模块文件 ✅
模块搜索顺序
当你运行 Python 程序时,解释器按照下面这个顺序来搜索模块:
✅sys.path组成顺序(标准机制)
顺序 | 路径 | 来源说明 |
---|---|---|
1️⃣ | 空字符串 ‘’ | 代表 当前脚本所在目录(或当前工作目录) |
2️⃣ | PYTHONPATH 环境变量指定的路径(如果有) | ✅ 用户可配置,优先级高,例如 pycharm 里面的 source-root |
3️⃣ | site-packages/ | pip 安装的第三方包目录 |
4️⃣ | 标准库路径(如 /usr/lib/python3.x) | 内置模块,比如 json、math |
5️⃣ | .pth 文件中注册的路径 | 如 virtualenv 中 .pth 会添加额外路径 |
解释器会按照 sys.path 中的顺序进行查找:
- 一旦找到了某个 模块名.py 或 包目录/(含 init.py),就停止搜索
- 所以前面的路径优先级更高
类型 | 是否在 sys.path 中 | 优先级高吗? | 举例 |
---|---|---|---|
’’ 当前执行目录 | ✅ 默认第一位 | ✅ | 脚本本地直接 import |
source root (PyCharm) | ✅ PyCharm 自动加 | 高(紧随其后) | IDE 推导 |
PYTHONPATH 设置路径 | ✅ 如果你 export 了 | 高 | PYTHONPATH=/my/app python main.py |
site-packages | ✅ 默认加入 | 中 | pip 安装的包 |
解释器自带路径 | ✅ | 最后才查 | json, math, asyncio 等 |
当前工作目录(’’) > PYTHONPATH > source root > site-packages > 标准库路径
init.py 作用
🧠 什么是__init__.py
它是一个特殊的 Python 文件,当你在一个目录下放置 init.py 后:
✅ Python 就会把这个目录当作一个包(package)
功能 | 说明 |
---|---|
✅ 表示该目录是一个包 | 没有它时,某些 Python 版本/工具无法识别为包(尤其早期) |
✅ 控制包的初始化行为 | 文件会在 import 包时自动执行 |
✅ 可以用作公共 API 管理 | 导入子模块、重命名、统一暴露接口 |
✅ 配合相对导入使用 | 包内模块可用 .module、..utils 导入 |
✅ 配合 all 控制 from xxx import * 的行为 | 显式限制导出的内容 |
你可以在 utils/init.py 中写:
app/
├── __init__.py
├── utils/
│ ├── __init__.py
│ ├── a.py
│ └── b.py
from .a import func_a
from .b import func_b
__all__ = ["func_a", "func_b"]
“统一导入管理”能力!
可以在外部:
from app.utils import func_a, func_b
自 Python 3.3 起,如果你没有 init.py,Python 也能识别包目录(叫 命名空间包)。
✅ 用来组织公共 API
# app/api/__init__.py
from .user import UserApi
from .order import OrderApi
外部只需:
from app.api import UserApi
✅ 初始化日志、环境变量(慎用)
# app/__init__.py
import os
os.environ["APP_MODE"] = "dev"
init.py 就是你显式声明“我这个文件夹是一个 Python 包”的护照,它让你拥有统一导入、初始化控制、模块组织等一整套包管理能力 —— 永远建议保留它 ✅