Python 模块系统全景:sys.path 决定 import 去哪找模块,四种方案解决跨项目导入,__main__ 与 -m 决定模块怎么运行。

模块与 sys.path

**模块(module)**就是一个 .py 文件,**包(package)**是包含 __init__.py 的目录。import 一个模块时,Python 按 sys.path 列表从前往后查找:

import sys
print(sys.path)
# ['', '/usr/lib/python3.10', ..., 'site-packages']
  • '':脚本所在目录(交互模式下是当前工作目录)
  • 标准库路径
  • 第三方库路径(site-packages)

绝大多数 import 报错的根源都是同一个:目标模块不在 sys.path 里。后面的跨项目导入、相对导入失败,都是这句话的不同表现形式。

import 查找顺序

sys.path 从前往后扫描,第一个命中的模块生效。当前目录下若有 random.py,会遮蔽标准库的 random——不要用标准库已有的名字命名自己的模块。确认模块实际来自哪里:

import random
print(random.__file__)

跨项目 import:四种方案

多个项目互相导入(比如把 models/Practical-RIFE 的代码 import 进当前项目),本质都是让对方的目录进入 sys.path。四种方案对比:

方案优点缺点适用场景
修改 sys.path简单直接需在每个入口添加代码快速调试、脚本开发
设置 PYTHONPATH一次设置全局生效依赖系统配置长期开发
优化项目结构结构规范、依赖明确需创建 setup.py长期维护项目
使用 importlib灵活、避免全局污染语法复杂动态加载场景

修改 sys.path

import sys, os
project_path = os.path.join(os.path.dirname(__file__), "models", "Practical-RIFE")
sys.path.append(project_path)
from video_interpolation_operator import VideoInterpolationOperator

设置 PYTHONPATH

Linux/MacOS:

export PYTHONPATH=/path/to/models/Practical-RIFE:/path/to/models/AMT:$PYTHONPATH

Windows:

set PYTHONPATH=C:\path\models\Practical-RIFE;C:\path\models\AMT;%PYTHONPATH%

优化项目结构(推荐)

创建 setup.py:

from setuptools import setup, find_packages
setup(name="practical-rife", version="0.1.0", packages=find_packages())

安装:

pip install -e .

使用 importlib

import importlib.util, os
spec = importlib.util.spec_from_file_location("module", "/path/to/module.py")
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)

模块运行:__main__ 与 -m

同一个模块有两种启动方式,差别就在 sys.path 的起点和包上下文上。

核心原理

python -m <模块名> 将指定模块以脚本方式运行:

  1. 按 sys.path 搜索模块
  2. 将当前工作目录加入 sys.path 前端
  3. 设置 __name__ = "__main__"

直接运行 python script.py 时,加入 sys.path 前端的则是脚本所在目录。

-m vs 直接运行

方式命令sys.path 起点相对导入
-mpython -m module当前工作目录✅ 正常
直接运行python script.py脚本所在目录❌ 可能失败

典型用法

# 运行标准库模块
python -m http.server 8000
python -m pip install --upgrade pip
 
# 运行调试器
python -m debugpy --listen 5678 script.py
 
# 运行自定义包(包内需要 __main__.py)
python -m my_package

相对导入为什么只有 -m 能跑通

my_project/
  ├── main.py
  └── utils/
      └── helper.py
# main.py
from .utils.helper import func  # 相对导入
# ❌ 直接运行失败
python main.py  # ImportError
 
# ✅ -m 运行成功
cd .. && python -m my_project.main

原因在包上下文:相对导入的锚点是「当前模块属于哪个包」。直接运行 main.py 时,它是顶层脚本,没有所属包,相对导入的 . 无从解析,抛出 ImportError;-m 则先把模块按包成员身份正常导入(包上下文完整),再执行其代码,相对导入自然成立。cd .. 保证当前工作目录——也就是 -m 的 sys.path 起点——能看到 my_project 这个包。

通用建议

  1. 使用虚拟环境隔离依赖
  2. 确保每个子项目包含 __init__.py
  3. 避免循环导入