Python 开发

python 搜索模块解析

JACIN··23 分钟阅读

目录#

[[toc]]

(Module Resolution / sys.path 搜索机制)

介绍#

当你运行:

jsx
python a.py

Python 会依次在以下路径中查找模块:

✅ 查找顺序是 sys.path

jsx
import sys
print(sys.path)
jsx
[
  '',                       # 当前工作目录(或脚本目录)
  '/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报错)

jsx
# 错误示例:直接运行
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

• 设置环境变量:

jsx
export PYTHONPATH=/your/project/root

• 或在脚本开头加:

jsx
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 这个目录下的包

jsx
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/
  • 或者手动在代码中加上:
jsx
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 安装包中的某个模块脚本,而是以“导入包”方式加载它们

jsx
import requests

然后它内部文件结构是这样的:

jsx
site-packages/
└── requests/
    ├── __init__.py
    ├── models.py
    └── utils.py

比如在 models.py:

jsx
from .utils import some_func  # ✅ 相对导入

这是合法的!因为解释器会认为你是以包方式导入的

jsx
import requests.models

而不是直接执行 models.py

jsx
# ❌ 错误示范(你不会这么干):
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 中写:

jsx
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"]

“统一导入管理”能力

可以在外部:

jsx
from app.utils import func_a, func_b

自 Python 3.3 起,如果你没有 init.py,Python 也能识别包目录(叫 命名空间包)。

✅ 用来组织公共 API

jsx
# app/api/__init__.py

from .user import UserApi
from .order import OrderApi

外部只需:

jsx
from app.api import UserApi

✅ 初始化日志、环境变量(慎用)

jsx
# app/__init__.py

import os
os.environ["APP_MODE"] = "dev"

init.py 就是你显式声明“我这个文件夹是一个 Python 包”的护照,它让你拥有统一导入、初始化控制、模块组织等一整套包管理能力 —— 永远建议保留它 ✅

评论

还没有评论,来发第一个吧