Administrator
发布于 2026-03-19 / 0 阅读
0
0

第七章:模块与包

第七章:模块与包

本章学习目标

  • 理解 Python 模块的概念和作用
  • 掌握模块的导入和使用方法
  • 学会创建和使用包
  • 深入理解 Python 的导入机制
  • 掌握虚拟环境的使用和管理

7.1 模块基础

7.1.1 什么是模块

模块是 Python 程序的基本组织单元。在 Python 中,每个 .py 文件就是一个模块。模块可以包含函数、类、变量和可执行代码。

使用模块的主要优点:

  1. 代码复用:模块可以被其他程序导入并使用
  2. 命名空间隔离:避免变量名冲突
  3. 维护性:将代码分割成独立的部分,便于管理
  4. 共享功能:可以将常用函数封装成模块供多处使用

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 导入模块时,会按照以下顺序搜索:

  1. 当前目录
  2. 环境变量 PYTHONPATH 中的目录
  3. 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 执行以下步骤:

  1. sys.modules 中查找模块是否已加载
  2. 如果没有,找到模块文件并加载
  3. 创建模块对象
  4. 将模块添加到 sys.modules
  5. 执行模块代码(定义函数、类、变量)
  6. 在当前命名空间中创建模块引用

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()

解决方法:

  • 将导入移到函数内部
  • 重构代码消除循环依赖

最佳实践

  1. 使用虚拟环境:始终为每个项目创建独立的虚拟环境

  2. 使用 requirements.txt 或 pyproject.toml:记录项目依赖

  3. 优先使用绝对导入:避免歧义

# ✓ 推荐
from my_package import module

# ✗ 不推荐
from . import module
  1. 合理组织包结构:遵循职责分离原则

  2. 使用 all 明确导出接口:控制公开 API

  3. 避免循环导入:重构代码结构

课后练习

练习 7.1:创建数学模块

创建一个 math_utils.py 模块,包含:

  • 圆面积计算函数
  • 判断质数的函数
  • 求最大公约数的函数
  • 计算斐波那契数列的函数

练习 7.2:创建工具包

创建一个 tools 包,包含:

  • string_utils.py:字符串工具函数
  • list_utils.py:列表工具函数
  • file_utils.py:文件操作函数
  • __init__.py:统一导出

练习 7.3:虚拟环境实践

  1. 创建一个新的虚拟环境
  2. 安装 requestsnumpy
  3. 导出 requirements.txt
  4. 创建一个新的虚拟环境并从 requirements.txt 安装

练习 7.4:模块导入练习

编写程序,动态导入标准库模块并列出其所有函数。

练习 7.5:包结构设计

设计一个学生管理系统的包结构,包含:

  • 学生增删改查功能
  • 成绩管理功能
  • 统计分析功能

本章小结

本章我们详细学习了 Python 的模块和包系统:

  1. 模块

    • 每个 .py 文件就是一个模块
    • 可以包含函数、类、变量和可执行代码
    • 通过 import 语句导入使用
    • 包含 __init__.py 的目录
    • 用于组织多个模块
    • 支持层次结构
  2. 导入机制

    • 多种导入方式
    • 模块搜索路径
    • 相对导入与绝对导入
  3. 虚拟环境

    • 隔离项目依赖
    • 避免版本冲突

掌握这些知识对于构建大型 Python 应用至关重要。

相关资源


评论