Python 模块化与包管理
一、模块(Module)
Python 中一个 .py 文件就是一个模块。模块可以包含函数、类、变量,通过 import 导入后就能复用。
1.1 导入方式
import math # 导入整个模块
print(math.sqrt(16)) # 4.0 — 通过模块名访问
from math import sqrt # 只导入 sqrt 函数
print(sqrt(16)) # 4.0 — 直接使用,不需要 math. 前缀
from math import * # 导入模块所有内容(不推荐,容易命名冲突)
print(sqrt(16)) # 4.0
import numpy as np # 起别名
print(np.array([1, 2, 3]))
from datetime import datetime as dt # 函数起别名
print(dt.now())
1.2 name 属性
每个模块都有 __name__ 属性:
- 直接运行该文件时,
__name__ 等于 "__main__"
- 被导入时,
__name__ 等于模块名
# mymodule.py
def greet():
print("Hello!")
# 只有直接运行时才执行,被 import 时不执行
if __name__ == '__main__':
greet()
python mymodule.py # 输出: Hello! — 直接运行,执行 greet()
# other.py
import mymodule
mymodule.greet() # Hello! — 被导入,if __name__ 不执行
这是 Python 的惯用模式,让模块既能被 import 复用,也能直接运行测试。
1.3 dir() 查看模块内容
import math
print(dir(math)) # 列出 math 模块所有属性和方法
print(dir()) # 不传参数,列出当前作用域所有名称
1.4 模块搜索路径
Python 导入模块时按以下顺序查找:
- 当前执行脚本所在目录
PYTHONPATH 环境变量指定的目录
- Python 标准库目录
.pth 文件指定的目录
import sys
print(sys.path) # 查看当前搜索路径列表
# 动态添加搜索路径
import sys
sys.path.append('/my/custom/path')
import mymodule # 现在可以找到了
二、包(Package)
包是模块的集合,本质上就是包含 __init__.py 文件的目录。
2.1 目录结构
myproject/
├── main.py # 入口文件
├── utils/ # 包:工具模块
│ ├── __init__.py # 标识这是一个包
│ ├── string_utils.py
│ └── file_utils.py
├── models/ # 包:数据模型
│ ├── __init__.py
│ ├── user.py
│ └── product.py
└── services/ # 包:业务逻辑
├── __init__.py
├── auth.py
└── order.py
__init__.py 可以是空文件(标记目录为包),也可以放初始化代码。
2.2 导入包
# 方式1:导入子模块(用全路径)
import utils.string_utils
utils.string_utils.capitalize("hello") # "Hello"
# 方式2:从包中导入子模块(推荐)
from utils import string_utils
string_utils.capitalize("hello")
# 方式3:直接导入函数/类
from utils.string_utils import capitalize
capitalize("hello")
# 方式4:导入整个包(触发 __init__.py 执行)
import utils
2.3 init.py 的作用
__init__.py 不仅是标记包存在的文件,还承担着包的公共 API 管理角色:
# utils/__init__.py
# 方式1:显式导入,控制对外暴露的接口
from .string_utils import capitalize, split_words
from .file_utils import read_file
# 方式2:定义 __all__,控制 from package import * 的行为
__all__ = ['capitalize', 'split_words', 'read_file']
# utils/string_utils.py
def capitalize(s):
return s.capitalize()
def split_words(s):
return s.split()
# 带下划线的函数视为内部实现,不对外暴露
def _internal_helper():
pass
这样导入时更简洁:
# 不需要写 utils.string_utils.capitalize
from utils import capitalize
capitalize("hello") # "Hello"
2.4 绝对导入 vs 相对导入
# 绝对导入 — 从项目根目录开始(推荐)
from utils.string_utils import capitalize
from models.user import User
from services.auth import login
# 相对导入 — 从当前包的位置开始(用 . 表示)
# 在 utils/string_utils.py 中:
from . import file_utils # 同级的 file_utils
from ..models import User # 上一级的 models 包
from ..services.auth import login # 上两级的 services.auth
. 表示当前目录
.. 表示上一级目录
... 表示上两级目录
重要规则: 相对导入只在包内部有效,脚本文件(入口文件)中不要用相对导入,用绝对导入。
# main.py(入口文件)— 只用绝对导入
from utils.string_utils import capitalize
from services.auth import login
2.5 常见的包结构
# 项目级结构
myproject/
├── pyproject.toml # 项目配置(现代方式)
├── setup.py # 项目配置(传统方式)
├── README.md
├── requirements.txt # 依赖列表
├── src/ # src 布局(推荐)
│ └── myproject/
│ ├── __init__.py
│ ├── main.py
│ ├── models/
│ │ ├── __init__.py
│ │ └── user.py
│ └── utils/
│ ├── __init__.py
│ └── helpers.py
└── tests/
├── __init__.py
└── test_main.py
# 简单项目(直接布局)
myproject/
├── main.py
├── config.py
├── database.py
├── utils/
│ ├── __init__.py
│ └── helpers.py
└── tests/
└── test_utils.py
三、all 变量
__all__ 控制 from module import * 的行为:
# utils/string_utils.py
def capitalize(s):
return s.capitalize()
def lowercase(s):
return s.lower()
def _internal():
pass
__all__ = ['capitalize', 'lowercase'] # 只暴露这两个
from utils.string_utils import *
# capitalize 和 lowercase 可用
# _internal 不会被导入
如果没有定义 __all__,import * 会导入所有不以 _ 开头的名称。
四、Python 3 命名空间包(Namespace Package)
Python 3.3+ 支持不需要 __init__.py 的命名空间包,允许一个包分布在多个目录中:
# 方式1:传统包 — 需要 __init__.py
mypackage/
├── __init__.py
├── module_a.py
# 方式2:命名空间包 — 不需要 __init__.py
mypackage/
├── module_a.py
命名空间包的典型用途是多个团队/仓库共同提供同一个包的不同部分:
# 比如公司内部的公共库,可能分布在不同仓库
# 仓库1: common/auth/
# 仓库2: common/utils/
# 安装后合并为一个 common 包
import common.auth
import common.utils
一般项目还是用传统包(带 __init__.py),更明确。
五、重新加载模块
修改模块代码后,已导入的模块不会自动更新,需要手动重新加载:
import mymodule
importlib.reload(mymodule)
实际开发中一般不需要这样做,重启 Python 进程更可靠。
六、标准库常用模块
import pathlib
p = pathlib.Path("data/output.txt")
p.parent.mkdir(parents=True, exist_ok=True) # 自动创建父目录
p.write_text("Hello!")