第七章:模块与包
本章学习目标
- 理解 Python 模块的概念和作用
- 掌握模块的导入和使用方法
- 学会创建和使用包
- 深入理解 Python 的导入机制
- 掌握虚拟环境的使用和管理
7.1 模块基础
7.1.1 什么是模块
模块是 Python 程序的基本组织单元。在 Python 中,每个 .py 文件就是一个模块。模块可以包含函数、类、变量和可执行代码。
使用模块的主要优点:
- 代码复用:模块可以被其他程序导入并使用
- 命名空间隔离:避免变量名冲突
- 维护性:将代码分割成独立的部分,便于管理
- 共享功能:可以将常用函数封装成模块供多处使用
7.1.2 创建第一个模块
让我们创建一个简单的模块来演示:
# my_module.py
"""这是一个示例模块
该模块提供一些数学函数和常量
"""
# 模块级变量(常量)
PI: float = 3.14159
E: float = 2.71828
# 函数定义
def greet(name: str) -> str:
"""问候函数"""
return f"Hello, {name}!"
def add(a: int, b: int) -> int:
"""返回两个数的和"""
return a + b
def factorial(n: int) -> int:
"""计算 n 的阶乘"""
if n < 0:
raise ValueError("负数没有阶乘")
if n == 0 or n == 1:
return 1
result: int = 1
for i in range(2, n + 1):
result *= i
return result
class Calculator:
"""计算器类"""
@staticmethod
def add(a: float, b: float) -> float:
return a + b
@staticmethod
def subtract(a: float, b: float) -> float:
return a - b
@staticmethod
def multiply(a: float, b: float) -> float:
return a * b
@staticmethod
def divide(a: float, b: float) -> float:
if b == 0:
raise ValueError("除数不能为零")
return a / b
7.1.3 导入模块
Python 提供了多种导入模块的方式:
# 方式1:导入整个模块
import my_module
# 使用模块中的函数
result = my_module.add(3, 5)
print(my_module.PI)
calc = my_module.Calculator()
# 方式2:从模块导入特定内容
from my_module import greet, PI, factorial
# 直接使用
message = greet("Alice")
print(PI)
print(factorial(5))
# 方式3:导入并指定别名
import my_module as mm
from my_module import factorial as fac
# 方式4:导入所有内容(不推荐)
from my_module import *
# 方式5:相对导入(包内使用)
# from . import module_name
# from ..package import module
7.1.4 模块的特殊变量
每个 Python 模块都有一些特殊的内置变量:
# __name__: 模块名称
# 当模块被直接运行时,__name__ 为 "__main__"
# 当模块被导入时,__name__ 为模块名
# 查看模块名
print(__name__) # __main__ (直接运行) 或 "my_module" (导入)
# __file__: 模块文件路径
print(__file__) # /path/to/my_module.py
# __doc__: 模块文档字符串
print(__doc__) # 模块顶部的文档字符串
# __all__: 导出列表
# 定义 from module import * 时导入的内容
__all__ = ["greet", "add", "Calculator"]
# __path__: 包特有的,模块所在的目录列表
# __package__: 包名称
7.1.5 模块的入口点
使用 __name__ == "__main__" 可以让模块在被直接运行时执行特定代码:
# my_module.py 末尾
if __name__ == "__main__":
# 只有直接运行此模块时才会执行
print("模块被直接运行")
# 可以添加测试代码
print(f"PI = {PI}")
print(f"5! = {factorial(5)}")
else:
print("模块被导入")
7.2 模块搜索路径
7.2.1 理解模块搜索路径
当 Python 导入模块时,会按照以下顺序搜索:
- 当前目录
- 环境变量
PYTHONPATH中的目录 - Python 安装目录(
site-packages等)
import sys
# 查看模块搜索路径
print(sys.path)
# 添加自定义搜索路径
sys.path.append("/path/to/my/modules")
7.2.2 sys.modules
sys.modules 是一个字典,包含了所有已导入的模块:
import sys
# 查看已导入的模块
print(list(sys.modules.keys())[:10])
# 检查模块是否已导入
if "my_module" in sys.modules:
print("my_module 已导入")
7.2.3 importlib
importlib 模块提供了动态导入模块的功能:
import importlib
# 动态导入
math_module = importlib.import_module("math")
print(math_module.sqrt(16)) # 4.0
# 动态重载
import importlib
import my_module
importlib.reload(my_module)
7.3 包 (Package)
7.3.1 什么是包
包是包含 __init__.py 文件的目录,用于组织多个模块。包可以包含子包,形成层次结构。
my_package/ # 包目录
__init__.py # 包初始化文件
module1.py # 模块1
module2.py # 模块2
sub_package/ # 子包
__init__.py
module3.py
7.3.2 创建包
首先创建以下目录结构和文件:
my_math/
__init__.py
basic.py
advanced.py
utils/
__init__.py
helpers.py
# my_math/__init__.py
"""my_math 包
这是一个数学工具包
"""
# 导入包时自动导入的内容
from .basic import add, subtract
from .advanced import factorial, fibonacci
# 定义包的版本
__version__ = "1.0.0"
# 可以指定 __all__
__all__ = ["add", "subtract", "factorial", "fibonacci"]
# my_math/basic.py
"""基础数学函数"""
def add(a: int | float, b: int | float) -> int | float:
return a + b
def subtract(a: int | float, b: int | float) -> int | float:
return a - b
def multiply(a: int | float, b: int | float) -> int | float:
return a * b
def divide(a: int | float, b: int | float) -> float:
if b == 0:
raise ZeroDivisionError("除数不能为零")
return a / b
# my_math/advanced.py
"""高级数学函数"""
def factorial(n: int) -> int:
"""计算阶乘"""
if n < 0:
raise ValueError("负数没有阶乘")
if n <= 1:
return 1
result: int = 1
for i in range(2, n + 1):
result *= i
return result
def fibonacci(n: int) -> list[int]:
"""生成斐波那契数列"""
if n < 0:
raise ValueError("n 必须为非负整数")
if n == 0:
return []
if n == 1:
return [0]
fibs: list[int] = [0, 1]
for _ in range(2, n):
fibs.append(fibs[-1] + fibs[-2])
return fibs
7.3.3 使用包
# 导入整个包
import my_math
# 使用包中的函数
result = my_math.add(3, 5)
fibs = my_math.fibonacci(10)
# 导入包中的特定模块
from my_math import basic, advanced
result = basic.add(3, 5)
fibs = advanced.fibonacci(10)
# 导入包中的特定函数
from my_math import add, factorial
print(add(3, 5)) # 8
print(factorial(5)) # 120
7.3.4 相对导入
在包内部,可以使用相对导入:
# my_math/utils/helpers.py
"""工具函数"""
from ..basic import add # 从父包导入 basic 模块
from .. import factorial # 从父包导入
def process_numbers(numbers: list[int]) -> int:
"""处理数字列表"""
return add(*numbers)
# my_math/__init__.py
# 使用相对导入
from .basic import add, subtract, multiply, divide
from .advanced import factorial, fibonacci
7.3.5 命名空间包 (Python 3.3+)
Python 3.3 引入了命名空间包,不需要 __init__.py:
namespace_pkg/
module1.py
module2.py
# 命名空间包会自动合并同名包
# 适合大型项目的模块分离
7.4 标准库模块
7.4.1 常用标准库概览
Python 自带"包含电池"(batteries included)的理念,提供了丰富的标准库:
| 模块 | 用途 |
|---|---|
math |
数学函数 |
random |
随机数生成 |
datetime |
日期和时间 |
json |
JSON 序列化 |
os |
操作系统接口 |
sys |
系统参数 |
re |
正则表达式 |
collections |
容器数据类型 |
itertools |
迭代器工具 |
functools |
函数式编程工具 |
7.4.2 math 模块
import math
# 基础数学常量
print(math.pi) # 3.141592653589793
print(math.e) # 2.718281828459045
# 数学函数
print(math.sqrt(16)) # 4.0
print(math.pow(2, 3)) # 8.0
print(math.exp(1)) # 2.718...
print(math.log(10)) # 自然对数
print(math.log10(100)) # 以10为底的对数
# 三角函数
print(math.sin(math.pi / 2)) # 1.0
print(math.cos(0)) # 1.0
print(math.tan(math.pi / 4)) # 1.0
# 取整函数
print(math.floor(3.7)) # 3
print(math.ceil(3.2)) # 4
print(math.trunc(3.7)) # 3
# 其他
print(math.gcd(48, 18)) # 6 最大公约数
print(math.factorial(5)) # 120 阶乘
7.4.3 random 模块
import random
# 随机整数
print(random.randint(1, 10)) # 1-10 之间的随机整数
print(random.randrange(0, 10, 2)) # 0-9 之间的随机偶数
# 随机浮点数
print(random.random()) # 0-1 之间的随机浮点数
print(random.uniform(1, 10)) # 1-10 之间的随机浮点数
# 随机选择
colors = ["red", "green", "blue"]
print(random.choice(colors)) # 随机选择一个
print(random.choices(colors, k=3)) # 随机选择多个(可重复)
print(random.sample(colors, 2)) # 随机选择多个(不重复)
# 随机打乱
numbers = [1, 2, 3, 4, 5]
random.shuffle(numbers) # 原地打乱
print(numbers)
# 设置随机种子
random.seed(42) # 固定种子,产生相同的随机序列
7.4.4 datetime 模块
from datetime import datetime, date, time, timedelta
# 获取当前时间
now = datetime.now()
today = datetime.today()
# 创建特定时间
dt = datetime(2024, 1, 15, 10, 30, 0)
d = date(2024, 1, 15)
t = time(10, 30, 0)
# 格式化
formatted = dt.strftime("%Y-%m-%d %H:%M:%S")
print(formatted) # 2024-01-15 10:30:00
# 解析
dt2 = datetime.strptime("2024-01-15 10:30:00", "%Y-%m-%d %H:%M:%S")
# 时间运算
tomorrow = now + timedelta(days=1)
yesterday = now - timedelta(days=1)
next_week = now + timedelta(weeks=1)
# 计算时间差
delta = datetime(2024, 1, 20) - datetime(2024, 1, 15)
print(delta.days) # 5
print(delta.seconds) # 0
7.4.5 json 模块
import json
# Python 对象转 JSON
data = {
"name": "Alice",
"age": 25,
"scores": [90, 85, 92],
"active": True,
"email": None
}
# 转为字符串
json_str = json.dumps(data, ensure_ascii=False, indent=2)
print(json_str)
# 写入文件
with open("data.json", "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
# JSON 转 Python 对象
data2 = json.loads(json_str)
# 读取文件
with open("data.json", "r", encoding="utf-8") as f:
data3 = json.load(f)
7.4.6 os 模块
import os
# 目录操作
os.getcwd() # 获取当前工作目录
os.chdir("/path") # 切换目录
os.listdir(".") # 列出目录内容
os.mkdir("dirname") # 创建目录
os.makedirs("a/b/c") # 创建多级目录
os.rmdir("dirname") # 删除目录
os.remove("file.txt") # 删除文件
# 路径操作
os.path.join("dir", "file.txt")
os.path.exists("path")
os.path.isfile("path")
os.path.isdir("path")
os.path.getsize("file")
# 环境变量
os.environ["PATH"]
os.getenv("HOME")
7.5 虚拟环境
7.5.1 为什么要使用虚拟环境
虚拟环境可以:
- 隔离不同项目的依赖
- 避免包版本冲突
- 保持系统环境清洁
7.5.2 创建虚拟环境
# 使用 venv(Python 3.3+ 内置)
python -m venv myenv
# 在 Windows 上激活
myenv\Scripts\activate
myenv\Scripts\activate.bat
myenv\Scripts\Activate.ps1 # PowerShell
# 在 macOS/Linux 上激活
source myenv/bin/activate
# 退出虚拟环境
deactivate
7.5.3 使用 virtualenv(更强大)
# 安装
pip install virtualenv
# 创建
virtualenv myenv
# 指定 Python 版本
virtualenv -p python3.10 myenv
# 激活和使用
source myenv/bin/activate
pip install requests
7.5.4 使用 conda
# 安装 Anaconda 或 Miniconda
# 创建环境
conda create -n myenv python=3.11
conda create -n myenv python=3.11 numpy pandas
# 激活环境
conda activate myenv
# 退出环境
conda deactivate
7.5.5 requirements.txt
# 导出依赖
pip freeze > requirements.txt
# 安装依赖
pip install -r requirements.txt
# 常用格式示例
# requests>=2.28.0
# numpy==1.24.0
# pandas>=1.5.0,<2.0.0
# matplotlib
# pytest
7.5.6 pyproject.toml(推荐)
[build-system]
requires = ["setuptools>=45", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "my_project"
version = "1.0.0"
description = "项目描述"
requires-python = ">=3.10"
dependencies = [
"requests>=2.28.0",
"numpy>=1.24.0",
]
[project.optional-dependencies]
dev = [
"pytest>=7.0.0",
"black>=22.0.0",
"mypy>=0.950",
]
7.6 导入机制详解
7.6.1 导入过程
当执行 import module 时,Python 执行以下步骤:
- 在
sys.modules中查找模块是否已加载 - 如果没有,找到模块文件并加载
- 创建模块对象
- 将模块添加到
sys.modules - 执行模块代码(定义函数、类、变量)
- 在当前命名空间中创建模块引用
7.6.2 all 属性
__all__ 定义了 from module import * 时导入的内容:
# my_module.py
__all__ = ["public_function", "PublicClass"]
def public_function():
pass
def _private_function(): # 不会通过 * 导入
pass
class PublicClass:
pass
class _PrivateClass: # 不会通过 * 导入
pass
7.6.3 循环导入问题
循环导入是指两个模块相互导入对方,可能导致问题:
# a.py
from b import func_b
def func_a():
return "a"
# b.py
from a import func_a # 可能出问题
def func_b():
return func_a()
解决方法:
- 将导入移到函数内部
- 重构代码消除循环依赖
最佳实践
使用虚拟环境:始终为每个项目创建独立的虚拟环境
使用 requirements.txt 或 pyproject.toml:记录项目依赖
优先使用绝对导入:避免歧义
# ✓ 推荐
from my_package import module
# ✗ 不推荐
from . import module
合理组织包结构:遵循职责分离原则
使用 all 明确导出接口:控制公开 API
避免循环导入:重构代码结构
课后练习
练习 7.1:创建数学模块
创建一个 math_utils.py 模块,包含:
- 圆面积计算函数
- 判断质数的函数
- 求最大公约数的函数
- 计算斐波那契数列的函数
练习 7.2:创建工具包
创建一个 tools 包,包含:
string_utils.py:字符串工具函数list_utils.py:列表工具函数file_utils.py:文件操作函数__init__.py:统一导出
练习 7.3:虚拟环境实践
- 创建一个新的虚拟环境
- 安装
requests和numpy包 - 导出 requirements.txt
- 创建一个新的虚拟环境并从 requirements.txt 安装
练习 7.4:模块导入练习
编写程序,动态导入标准库模块并列出其所有函数。
练习 7.5:包结构设计
设计一个学生管理系统的包结构,包含:
- 学生增删改查功能
- 成绩管理功能
- 统计分析功能
本章小结
本章我们详细学习了 Python 的模块和包系统:
模块:
- 每个 .py 文件就是一个模块
- 可以包含函数、类、变量和可执行代码
- 通过
import语句导入使用
包:
- 包含
__init__.py的目录 - 用于组织多个模块
- 支持层次结构
- 包含
导入机制:
- 多种导入方式
- 模块搜索路径
- 相对导入与绝对导入
虚拟环境:
- 隔离项目依赖
- 避免版本冲突
掌握这些知识对于构建大型 Python 应用至关重要。