工作目录
在深入相对路径之前,你必须理解 当前工作目录 的概念。

当前工作目录 是 Python 解释器在执行脚本时,默认查找文件和模块的起始位置,你可以把它想象成一个“默认文件夹”。
- 当你使用
import my_module时,Python 首先会在当前工作目录下寻找my_module.py或my_module文件夹。 - 当你使用
open('data.txt', 'r')打开文件时,Python 会在当前工作目录下寻找data.txt。
你可以通过以下代码随时查看和更改当前工作目录:
import os
# 查看当前工作目录
print("当前工作目录是:", os.getcwd())
# 更改当前工作目录 (切换到项目根目录)
# os.chdir('/path/to/your/project')
相对路径的四种形式
相对路径是相对于 当前工作目录 的路径,Python 提供了四种主要的相对路径形式:
import module_in_same_dir(同级导入)import package.submodule(向下导入)import ..sibling_module(向上导入)import ...module_in_grandparent(向上多级导入)
为了更好地演示,我们创建一个示例项目结构:

my_project/
├── main.py # 我们要运行的脚本
├── utils.py # 与 main.py 同级的模块
└── my_package/
├── __init__.py # 使 my_package 成为一个包
├── core.py # 包内的核心模块
└── subpackage/
├── __init__.py
└── helper.py # 更深层的模块
假设我们所有的操作都在 my_project 目录下进行,my_project 就是我们的 当前工作目录。
导入同级目录的模块
场景: 从 main.py 导入 utils.py。
main.py 和 utils.py 在同一个目录 (my_project) 下。
代码 (main.py):

# 直接导入模块名
import utils
# 或者使用 as 给一个别名
import utils as ut
# 调用 utils.py 中的函数
utils.print_message("Hello from main.py!")
# ut.print_message("Hello from main.py!")
解释: 因为 utils.py 和 main.py 在同一个工作目录下,所以可以直接通过模块名 utils 来导入。
导入子包/子目录的模块
场景: 从 main.py 导入 my_package/core.py。
my_package 是一个包含 __init__.py 的包,core.py 在这个包内部。
代码 (main.py):
# 导入整个包,然后通过包名访问模块 import my_package my_package.core.process_data() # 直接导入包内的特定模块 from my_package import core core.process_data() # 导入模块中的特定函数或类 from my_package.core import process_data process_data()
解释: Python 会自动查找当前工作目录下的子包,导入路径 my_package.core 表示“在 my_package 包里,找到 core 模块”。
导入上级目录的模块
场景: 从 my_package/core.py 导入 main.py 所在目录的 utils.py。
这是一个非常常见的需求,但也是最容易出错的地方。
代码 (my_package/core.py):
# 方法一:修改 sys.path (不推荐,但有效)
import sys
import os
# 获取当前文件 (core.py) 所在目录的绝对路径
current_dir = os.path.dirname(os.path.abspath(__file__))
# 获取上级目录 (my_project) 的绝对路径
parent_dir = os.path.dirname(current_dir)
# 将上级目录添加到 sys.path 的最前面
sys.path.insert(0, parent_dir)
# 现在就可以正常导入了
import utils
utils.print_message("Hello from core.py!")
# 方法二:使用相对导入 (强烈推荐)
# 注意:相对导入只能在包内部使用,不能用于主脚本(如 main.py)
# from .. import utils
# utils.print_message("Hello from core.py (using relative import)!")
解释:
sys.path方法:sys.path是一个 Python 查找模块的路径列表,通过动态地将上级目录添加到这个列表中,Python 就能找到utils模块,这种方法很灵活,但会使代码依赖于运行环境,降低了可移植性。- 相对导入 ():
- 表示当前目录。
- 表示上一级目录。
- 表示上上级目录,以此类推。
- 关键点: 相对导入 只能用于包内部的模块 (即被
import的文件,而不是直接运行的脚本),如果你在main.py中尝试from .. import something,会直接报错ImportError: attempted relative import beyond top-level package。
导入上级目录的兄弟模块
场景: 从 my_package/subpackage/helper.py 导入 my_package/core.py。
代码 (my_package/subpackage/helper.py):
# 使用相对导入 from .. import core # 调用 core.py 中的函数 core.process_data()
解释: 表示从 subpackage 目录向上跳一级,到达 my_package 目录,然后导入 core 模块,这比使用绝对路径 from my_package import core 更清晰,且不受工作目录变化的影响。
最佳实践与常见陷阱
脚本 vs. 模块
- 主脚本: 像
main.py这样直接通过python main.py运行的文件,不应该使用相对导入,它处于整个包结构的“顶层”。 - 模块: 像
utils.py,core.py,helper.py这样被其他文件导入的文件,应该优先使用相对导入,这样可以保持代码的清晰和与项目结构的紧密耦合。
if __name__ == "__main__": 的妙用
当你编写一个既想作为独立脚本运行,又想作为模块被导入的文件时,可以使用这个技巧。
示例 (my_package/core.py):
# ... 核心代码 ...
def process_data():
print("Processing data in core.py")
# --- 相对导入部分 ---
# 只有当这个文件被当作模块导入时,才执行相对导入
# 这样,当直接运行 python my_package/core.py 时就不会报错
if __name__ != "__main__":
# 这个模块可能会用到上级目录的 utils
# from .. import utils
pass
# --- 独立脚本部分 ---
# 只有当这个文件被直接运行时,才执行下面的代码
if __name__ == "__main__":
print("core.py is being run directly.")
# 你可以在这里写一些测试代码或命令行接口
process_data()
__init__.py 的作用
- 将目录标记为包:
__init__.py文件使得一个普通目录可以被 Python 解释器识别为一个“包”,没有它,你就无法使用import my_package.core这样的语法。 - 包的初始化: 可以在
__init__.py中执行包的初始化代码,或者(在较新版本的 Python 中)留空,它仍然有效。 - 控制导入: 你可以在
__init__.py中决定将哪些模块或函数暴露给外部。
示例 (my_package/__init__.py):
# 当执行 from my_package import * 时,只会导入 core 模块 from . import core # 你也可以直接在 __init__.py 中导入函数,让调用更方便 # from .core import process_data
这样,在 main.py 中,你就可以这样做:
from my_package import core # 因为 __init__.py 中已经这么做了
| 导入场景 | 推荐方法 | 示例 | 注意事项 |
|---|---|---|---|
| 同级模块 | 直接导入 | import utils |
最简单直接的方式。 |
| 子包/子目录模块 | 绝对导入 | from my_package import core |
清晰,不依赖工作目录。 |
| 上级目录模块 | 相对导入 (在包内) | from .. import utils |
强烈推荐,只能在包内部使用。 |
| 上级目录模块 | sys.path 修改 (备用) |
sys.path.insert(0, '..') |
不推荐,破坏代码的可移植性。 |
| 兄弟模块 | 相对导入 (在包内) | from .. import core |
清晰,是包内通信的最佳实践。 |
核心思想:
- 主脚本 (
main.py) 使用 绝对路径 或同级导入。 - 包内模块 之间优先使用 相对导入,因为它更健壮、更清晰。
- 理解 工作目录 是解决所有
import问题的第一步。
