模块和包
在 Python 中,代码组织的方式主要有两种:

- 模块:一个
.py文件,就是一个模块,文件名就是模块名(去掉.py后缀)。my_module.py就是一个名为my_module的模块。 - 包:一个包含
__init__.py文件的目录,包用于组织多个相关的模块。__init__.py文件可以是一个空文件,它的存在告诉 Python 这个目录应该被当作一个包来处理。
导入同一目录下的模块
这是最简单的情况,假设你有以下文件结构:
my_project/
├── main.py
└── my_module.py
my_module.py 的内容:
# my_module.py
def greet(name):
"""一个简单的问候函数"""
return f"Hello, {name} from my_module!"
PI = 3.14159
main.py 的内容:
# main.py
# 直接导入模块名
import my_module
# 使用模块名.函数名/变量名 来访问
message = my_module.greet("Alice")
print(message)
# 访问模块中的变量
print(f"The value of PI is: {my_module.PI}")
如何运行:
在终端中,进入到 my_project 目录,然后运行:

python main.py
输出:
Hello, Alice from my_module!
The value of PI is: 3.14159
说明:
当 Python 执行 import my_module 时,它会去当前执行的脚本所在的目录(这里是 my_project)寻找名为 my_module.py 的文件,找到后,就会执行它并将其内容加载到 my_module 这个命名空间中。
导入子目录下的模块(包)
当你的项目变大,需要将模块放入子目录时,你就需要创建一个包。
假设文件结构如下:

my_project/
├── main.py
└── utils/
├── __init__.py # 必须存在,可以为空
└── string_utils.py
utils/string_utils.py 的内容:
# utils/string_utils.py
def reverse_string(text):
"""反转一个字符串"""
return text[::-1]
utils/__init__.py 的内容:
(这个文件可以为空,但强烈建议保留它,即使只写一行注释 # utils package)
main.py 的内容:
现在你有两种主要的导入方式:
方式 A:导入整个包,然后使用 包.模块 的方式
# main.py
# 导入 utils 包
import utils
# 然后通过 utils.模块名 来访问模块中的内容
reversed_text = utils.string_utils.reverse_string("Python")
print(reversed_text)
方式 B:从包中导入特定的模块(更常用)
# main.py
# 从 utils 包中导入 string_utils 模块
from utils import string_utils
# 直接使用模块名.函数名
reversed_text = string_utils.reverse_string("Python")
print(reversed_text)
方式 C:从模块中导入特定的函数/变量
# main.py
# 从 utils.string_utils 模块中直接导入 reverse_string 函数
from utils.string_utils import reverse_string
# 直接使用函数名,非常方便
reversed_text = reverse_string("Python")
print(reversed_text)
如何运行:
同样,在 my_project 目录下运行 python main.py,以上三种方式都能正常工作并输出 nohtyP。
导入上级目录或其他目录的模块
这是最常见也最容易让人困惑的情况,Python 默认只会从当前工作目录和标准库路径中查找模块,如果你想导入不在这些位置的模块,你需要修改 Python 的模块搜索路径。
假设文件结构如下:
project_root/
├── main.py
├── src/
│ └── my_module.py
└── tests/
└── test_main.py
目标:
在 tests/test_main.py 中,想导入 src/my_module.py。
方法 1:修改 sys.path(最直接,但不推荐用于生产环境)
在需要导入的脚本中,动态地将上级目录(或目标目录)添加到 sys.path 列表中。
tests/test_main.py 的内容:
# tests/test_main.py
import sys
import os
# 获取当前文件所在的目录 (tests)
current_dir = os.path.dirname(os.path.abspath(__file__))
# 获取上级目录 (project_root)
parent_dir = os.path.dirname(current_dir)
# 将上级目录添加到 sys.path 的最前面
sys.path.insert(0, parent_dir)
# 现在就可以像在 main.py 中一样直接导入了
import src.my_module as my_module
# 或者 from src import my_module
# 或者 from src.my_module import greet
message = my_module.greet("Tester")
print(message)
说明:
os.path.abspath(__file__)获取test_main.py的绝对路径。os.path.dirname()获取路径的目录部分。sys.path.insert(0, ...)将路径插入到sys.path的开头,确保 Python 优先搜索这个路径。
优点:简单直接,不需要安装任何东西。
缺点:修改了全局的 sys.path,可能会影响其他模块的导入,并且只在当前脚本运行时有效,不推荐在大型项目或库的代码中使用。
方法 2:设置 PYTHONPATH 环境变量(推荐)
PYTHONPATH 是一个环境变量,它的作用是告诉 Python 解释器除了默认路径外,还应该去哪些目录里寻找模块。
如何设置(以 Linux/macOS 为例): 在终端中,运行以下命令(临时生效,关闭终端后失效):
# export PYTHONPATH=$PYTHONPATH:/path/to/your/project_root # 假设 project_root 在 /home/user/my_project export PYTHONPATH=$PYTHONPATH:/home/user/my_project # 然后运行你的测试脚本 python tests/test_main.py
如何设置(以 Windows 为例): 在命令提示符中:
# set PYTHONPATH=%PYTHONPATH%;C:\path\to\your\project_root # 假设 project_root 在 C:\Users\YourUser\my_project set PYTHONPATH=%PYTHONPATH%;C:\Users\YourUser\my_project # 然后运行你的测试脚本 python tests\test_main.py
在 test_main.py 中,你就可以直接导入了,无需修改 sys.path:
# tests/test_main.py
# 因为 project_root 已经在 PYTHONPATH 中了
import src.my_module as my_module
message = my_module.greet("Tester from PYTHONPATH")
print(message)
优点:比修改 sys.path 更干净,是配置开发环境的常用方法。
缺点:需要手动设置环境变量。
方法 3:使用相对导入(仅用于包内部)
重要:相对导入只能在包的内部模块中使用,不能在最顶层的脚本(如 main.py)中使用。
假设你的项目结构如下,并且你希望将 main.py 也变成一个模块,放在 src 包里:
project_root/
├── src/
│ ├── __init__.py
│ ├── main.py # 作为 src 包的一部分
│ └── my_module.py
└── tests/
└── test_main.py
你想在 src/main.py 中导入 src/my_module.py。
src/main.py 的内容:
# src/main.py
# 使用点号 . 表示当前包
# .my_module 表示 src 包下的 my_module 模块
from . import my_module
# 或者 from .my_module import greet
message = my_module.greet("Main Script")
print(message)
如何运行:
你不能直接用 python src/main.py 来运行,因为相对导入的上下文是包,而不是脚本,你需要使用 -m 标志来告诉 Python 将一个模块作为脚本来运行。
# 从 project_root 目录运行 python -m src.main
说明:
- 代表当前包。
- 代表上一级包。
- 代表上上级包,以此类推。
优点:非常清晰,明确指出了模块间的相对关系。
缺点:使用方式有特殊要求(-m),不能直接作为脚本运行。
总结与最佳实践
| 场景 | 文件结构 | 推荐方法 | 示例 |
|---|---|---|---|
| 同一目录 | main.py, my_module.py |
import my_module |
import my_module |
| 子目录(包) | main.py, utils/mod.py |
from utils import mod |
from utils import string_utils |
| 上级目录 | main.py, src/mod.py |
设置 PYTHONPATH |
在终端设置 export PYTHONPATH=... |
| 包内部 | pkg/main.py, pkg/mod.py |
使用相对导入 | from . import mod (运行时用 python -m pkg.main) |
核心要点:
- 模块就是
.py文件,包是带__init__.py的目录。 import module会加载整个模块,通过module.func访问。from module import func只加载指定的函数/变量,可以直接使用func。- 跨目录导入的核心是让 Python 能找到你的文件,最灵活和标准的方式是设置
PYTHONPATH。 - 避免在代码中写死路径修改
sys.path,除非是临时的调试脚本。 - 相对导入 (
from . import ...) 是包内部通信的优雅方式,但要记得用python -m package.module来运行。
希望这个详细的解释能帮助你完全理解 Python 的自定义导入机制!
