核心结论

场景

基准点

写法

坑在哪

模块导入 import

sys.path

列表

import utils

和文件实际位置无关

包内相对导入

当前包的 __init__.py

所在目录

from . import sibling

只能在包内用,不能直接运行

读取文件

当前工作目录(cwd)

open('data.txt')

运行方式不同,cwd 不同

脚本自身定位

脚本文件真实路径 __file__

os.path.dirname(__file__)

最可靠的锚点


一、为什么会乱?根因是"基准点"不同

project/
├── main.py          # 入口
├── utils.py         # 工具
└── pkg/
    ├── __init__.py
    ├── a.py
    └── b.py

你怎么运行

当前工作目录 cwd

import utils

能找到吗

open('data.txt')

找哪里

python main.py

project/

✅ 能(cwd 在 sys.path)

project/data.txt

python pkg/a.py

project/

✅ 能

project/data.txt

cd pkg && python a.py

project/pkg/

❌ 不能

project/pkg/data.txt

关键:open() 和相对导入看的是 cwd,不是文件所在目录。


二、四种调用写法对比

写法1:直接 import(看 sys.path)

python


# main.py
import utils          # 从 sys.path 里找
from pkg import a     # pkg 必须是包(有 __init__.py)
  • ✅ 简单,Python 原生支持
  • ❌ 依赖 sys.path,换个目录运行就崩

写法2:相对导入(看包结构)

python


# pkg/a.py
from . import b           # 同级
from .b import func       # 同级的 b 模块
from .. import utils      # 上一级(pkg 的父目录)
  • ✅ 包内耦合清晰,重构方便
  • 不能直接运行python pkg/a.py 会报 ImportError: attempted relative import
  • 🔧 必须用 python -m pkg.a 运行

写法3:用 __file__ 定位(最稳)

python


# 任何文件里都能用
import os
BASE_DIR = os.path.dirname(os.path.abspath(__file__))  # 脚本真实所在目录

data_path = os.path.join(BASE_DIR, 'data.txt')
with open(data_path) as f:
    ...
  • ✅ 不管怎么运行,路径都对
  • ✅ 读文件、加载配置的首选
  • ❌ 写起来稍长

Python 3.4+ 推荐用 pathlib

python


from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent
data_path = BASE_DIR / 'data.txt'

写法4:动态加 sys.path(临时方案)

python


# main.py
import sys
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
import utils  # 现在能找到了
  • ✅ 不改项目结构就能 import
  • ❌ 污染全局,多个文件重复加会乱
  • ⚠️ 调试用可以,生产环境不推荐

三、最佳实践建议

需求

推荐写法

包内模块互相调用

相对导入 from . import x

,用 python -m

运行

读取配置/数据文件

Path(__file__).parent / 'config.json'

跨包调用工具函数

顶层 import utils

,确保入口目录在 sys.path

临时调试

sys.path.append(...)

可以,但记得删


四、一句话总结

importsys.pathopen()cwd__file__ 看脚本自己。 搞清基准点,写法自然就对了。

更多推荐