杰瑞科技汇

Python import相对路径如何正确使用?

工作目录

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

Python import相对路径如何正确使用?-图1
(图片来源网络,侵删)

当前工作目录 是 Python 解释器在执行脚本时,默认查找文件和模块的起始位置,你可以把它想象成一个“默认文件夹”。

  • 当你使用 import my_module 时,Python 首先会在当前工作目录下寻找 my_module.pymy_module 文件夹。
  • 当你使用 open('data.txt', 'r') 打开文件时,Python 会在当前工作目录下寻找 data.txt

你可以通过以下代码随时查看和更改当前工作目录:

import os
# 查看当前工作目录
print("当前工作目录是:", os.getcwd())
# 更改当前工作目录 (切换到项目根目录)
# os.chdir('/path/to/your/project')

相对路径的四种形式

相对路径是相对于 当前工作目录 的路径,Python 提供了四种主要的相对路径形式:

  1. import module_in_same_dir (同级导入)
  2. import package.submodule (向下导入)
  3. import ..sibling_module (向上导入)
  4. import ...module_in_grandparent (向上多级导入)

为了更好地演示,我们创建一个示例项目结构:

Python import相对路径如何正确使用?-图2
(图片来源网络,侵删)
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.pyutils.py 在同一个目录 (my_project) 下。

代码 (main.py):

Python import相对路径如何正确使用?-图3
(图片来源网络,侵删)
# 直接导入模块名
import utils
# 或者使用 as 给一个别名
import utils as ut
# 调用 utils.py 中的函数
utils.print_message("Hello from main.py!")
# ut.print_message("Hello from main.py!")

解释: 因为 utils.pymain.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 问题的第一步。
分享:
扫描分享到社交APP
上一篇
下一篇