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 导入模块时按以下顺序查找:

  1. 当前执行脚本所在目录
  2. PYTHONPATH 环境变量指定的目录
  3. Python 标准库目录
  4. .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 进程更可靠。

六、标准库常用模块

模块用途
math数学运算
os文件和目录操作
sys系统参数
random随机数
datetime日期和时间
jsonJSON 解析
re正则表达式
collections高级数据结构(deque、defaultdict)
itertools迭代器工具
functools高阶函数工具(lru_cache、reduce)
pathlib路径操作(推荐替代 os.path)
logging日志记录
typing类型注解
dataclasses数据类
import pathlib
p = pathlib.Path("data/output.txt")
p.parent.mkdir(parents=True, exist_ok=True)  # 自动创建父目录
p.write_text("Hello!")