包结构
包(Package)是组织多个模块的方式。它是一个包含 __init__.py 文件的目录,目录下的 .py 文件都是包的子模块。包让大型项目的代码结构更清晰,避免模块名冲突。
基本结构
mypackage/
__init__.py # 包初始化文件,必须存在
module1.py
module2.py
subpackage/
__init__.py
module3.py
__init__.py 可以是空文件,它的存在告诉 Python:"这个目录是一个包,不是普通目录。"Python 2 中,__init__.py 必须存在;Python 3.3+ 中,没有 __init__.py 的目录也可以被识别为"命名空间包",但传统包仍然建议保留 __init__.py。
导入包内模块
# 导入整个子模块
import mypackage.module1
mypackage.module1.func()
# 从包导入指定名称
from mypackage import module1
module1.func()
# 从子模块导入指定函数
from mypackage.module1 import func
func()
# 导入子包
from mypackage import subpackage
subpackage.module3.func()
# 直接导入子包的模块
import mypackage.subpackage.module3
init.py 的作用
__init__.py 在包被导入时执行,常用于:
1. 初始化包级变量:
# mypackage/__init__.py
VERSION = "1.0.0"
AUTHOR = "Alice"
from module1 import main_func # 让 main_func 可以直接通过包访问
import mypackage
print mypackage.VERSION # 1.0.0
mypackage.main_func() # 直接调用,无需 mypackage.module1.main_func()
2. 控制 from mypackage import * 的行为:
# mypackage/__init__.py
__all__ = ["module1", "module2"] # 定义 * 导入时暴露的名称
from mypackage import * # 只导入 module1 和 module2
3. 执行初始化代码:
# mypackage/__init__.py
import logging
logging.basicConfig(level=logging.INFO)
print "mypackage initialized"
相对导入
包内模块可以使用相对导入引用同级或上级模块:
# mypackage/module2.py
from . import module1 # 导入同级模块 module1
from .subpackage import module3 # 导入子包中的 module3
from .. import otherpackage # 导入上级目录的包(需要合适的包结构)
. 表示当前包,.. 表示上级包。相对导入让包内部引用更清晰,不依赖包名。
⚠️ 相对导入只能在包内使用,直接运行模块文件时会报错:
$ python mypackage/module2.py
# ValueError: Attempted relative import in non-package
解决方法是使用 python -m 运行:
$ python -m mypackage.module2
实际项目结构示例
myproject/
setup.py # 包安装配置
README.md
myproject/ # 主包
__init__.py
__main__.py # python -m myproject 的入口
core/
__init__.py
engine.py
parser.py
utils/
__init__.py
helpers.py
validators.py
tests/
test_engine.py
test_parser.py
# myproject/__init__.py
from core.engine import Engine
from utils.helpers import format_date
__version__ = "1.0.0"
__all__ = ["Engine", "format_date"]
# myproject/core/engine.py
from ..utils.helpers import format_date
class Engine(object):
def run(self):
print "Engine started at", format_date()
包与模块的命名
- 包名和模块名使用小写,避免与标准库冲突
- 不要用
test、string、random等标准库名作为包名或模块名 - 单文件模块用下划线命名:
data_loader.py - 包目录用简短小写:
core/、utils/、models/