用户工具

站点工具


python:第十章模块与包

第十章 模块与包

模块和包是Python中组织代码的核心机制。随着程序规模的增长,将代码分割成多个文件和目录变得至关重要。本章将详细介绍模块、包的概念以及如何使用它们来构建可维护的大型项目。

10.1 模块的概念

模块是包含Python代码的文件,以.py为扩展名。模块可以定义函数、类和变量,也可以包含可执行的代码。使用模块可以将大程序分解为可管理的部分,提高代码的可重用性和组织性。

10.1.1 为什么使用模块

模块提供了多种优势:

  • 代码复用:编写一次,多处使用
  • 命名空间隔离:避免命名冲突
  • 可维护性:将相关功能组织在一起
  • 抽象:隐藏实现细节,暴露清晰接口

10.1.2 创建模块

创建模块非常简单,只需将Python代码保存为.py文件即可。

文件:math_utils.py

"""数学工具模块 - 提供常用的数学计算功能"""
 
PI = 3.14159265359
E = 2.71828182846
 
def factorial(n):
    """计算阶乘"""
    if n < 0:
        raise ValueError("n必须是非负整数")
    if n == 0 or n == 1:
        return 1
    result = 1
    for i in range(2, n + 1):
        result *= i
    return result
 
def is_prime(n):
    """判断是否为素数"""
    if n < 2:
        return False
    for i in range(2, int(n**0.5) + 1):
        if n % i == 0:
            return False
    return True
 
def gcd(a, b):
    """计算最大公约数(欧几里得算法)"""
    while b:
        a, b = b, a % b
    return a
 
def lcm(a, b):
    """计算最小公倍数"""
    return abs(a * b) // gcd(a, b)
 
def fibonacci(n):
    """生成斐波那契数列的前n项"""
    if n <= 0:
        return []
    elif n == 1:
        return [0]
 
    fib = [0, 1]
    for i in range(2, n):
        fib.append(fib[i-1] + fib[i-2])
    return fib
 
# 模块级别的代码,在导入时执行
print(f"模块 {__name__} 已加载")

10.2 导入模块

Python提供了多种导入模块的方式,每种方式适用于不同的场景。

10.2.1 import语句

最基本的方式是使用import语句导入整个模块。

import math_utils
 
# 使用模块中的函数
print(math_utils.factorial(5))      # 输出: 120
print(math_utils.is_prime(17))      # 输出: True
print(math_utils.gcd(48, 18))       # 输出: 6
print(math_utils.PI)                # 输出: 3.14159265359
 
# 生成斐波那契数列
fib_sequence = math_utils.fibonacci(10)
print(fib_sequence)                 # 输出: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

10.2.2 from...import语句

从模块中导入特定的函数或变量,可以直接使用而无需模块名前缀。

from math_utils import factorial, is_prime, PI
 
print(factorial(5))                 # 直接调用,无需math_utils.前缀
print(is_prime(17))
print(PI)

10.2.3 使用别名

使用as关键字为模块或函数指定别名,这在模块名较长或避免命名冲突时特别有用。

import math_utils as mu
from math_utils import factorial as fact
 
print(mu.gcd(48, 18))
print(fact(5))
 
# 避免与内置math模块冲突
import math
import math_utils as mymath
 
print(math.pi)                      # 内置math模块
print(mymath.PI)                    # 自定义math_utils模块

10.2.4 导入所有内容

使用星号(*)导入模块中的所有公共名称,但不推荐这种方式,因为它会污染命名空间。

from math_utils import *
 
print(factorial(5))
print(gcd(48, 18))
print(PI)

注意:星号导入只导入模块的all列表中定义的名称,如果没有定义all,则导入所有不以 underscore 开头的名称。

10.3 模块搜索路径

当导入模块时,Python按照特定顺序在以下位置搜索:

1. 当前目录
2. PYTHONPATH环境变量中的目录
3. 标准库目录
4. 第三方包目录(site-packages)
import sys
 
# 查看模块搜索路径
for path in sys.path:
    print(path)
 
# 动态添加搜索路径
sys.path.append('/path/to/my/modules')
sys.path.insert(0, '/priority/path')  # 添加到最前面

10.4 模块的特殊属性

10.4.1 __name__属性

name属性在模块被直接运行时值为'main',在被导入时为模块名。这常用于编写既可导入又可运行的模块。

# 在math_utils.py末尾添加
if __name__ == "__main__":
    # 测试代码,仅在直接运行时执行
    print("运行测试...")
    assert factorial(0) == 1
    assert factorial(5) == 120
    assert is_prime(2) == True
    assert is_prime(4) == False
    assert gcd(48, 18) == 6
    print("所有测试通过!")

10.4.2 __all__属性

all列表定义了当使用“from module import *”时应导入的名称。

# 在math_utils.py开头添加
__all__ = ['factorial', 'is_prime', 'gcd', 'lcm', 'fibonacci', 'PI', 'E']

10.4.3 其他常用属性

import math_utils
 
print(math_utils.__name__)          # 模块名
print(math_utils.__file__)          # 模块文件路径
print(math_utils.__doc__)           # 模块文档字符串
print(dir(math_utils))              # 模块中所有属性和方法

10.5 包的概念

包是包含多个模块的目录,通过层次化的方式组织代码。包目录必须包含一个init.py文件(Python 3.3+可选,但建议保留)。

10.5.1 包的结构

典型的包结构如下:

myproject/
├── __init__.py
├── core/
│   ├── __init__.py
│   ├── models.py
│   └── utils.py
├── web/
│   ├── __init__.py
│   ├── routes.py
│   └── templates/
└── db/
    ├── __init__.py
    ├── connection.py
    └── queries.py

10.5.2 __init__.py文件

init.py文件在包被导入时执行,常用于:

  • 初始化包级别的变量
  • 控制包的导入行为
  • 简化包的公共接口

文件:mypackage/init.py

"""mypackage - 示例包"""
 
__version__ = '1.0.0'
__author__ = 'Python Developer'
 
# 简化导入,让用户可以直接 from mypackage import some_function
from .core.utils import helper_function
from .web.routes import setup_routes
 
# 定义__all__控制星号导入
__all__ = ['helper_function', 'setup_routes', '__version__']

10.6 相对导入与绝对导入

10.6.1 绝对导入

从项目的根目录开始的完整导入路径。

# 绝对导入
from mypackage.core.models import User
from mypackage.db.connection import Database

10.6.2 相对导入

使用点号表示相对位置,适用于包内部模块之间的导入。

# 在 mypackage/web/routes.py 中
from ..core.models import User      # 上级目录的core模块
from ..db.connection import Database
from .templates import render        # 同级目录的templates模块
 
# .  当前目录
# .. 上级目录
# ...上上级目录,以此类推

注意:相对导入只能在包内部使用,不能直接运行包含相对导入的模块。

10.7 常用标准库模块

Python标准库包含大量实用的模块:

10.7.1 os模块 - 操作系统接口

import os
 
# 文件和目录操作
print(os.getcwd())                  # 获取当前工作目录
os.chdir('/tmp')                    # 改变工作目录
os.mkdir('newdir')                  # 创建目录
os.makedirs('a/b/c', exist_ok=True) # 递归创建目录
os.remove('file.txt')               # 删除文件
os.rmdir('empty_dir')               # 删除空目录
os.rename('old.txt', 'new.txt')     # 重命名
 
# 路径操作
path = os.path.join('folder', 'subfolder', 'file.txt')
print(os.path.exists(path))         # 检查路径是否存在
print(os.path.isfile(path))         # 是否为文件
print(os.path.isdir(path))          # 是否为目录
print(os.path.basename(path))       # 文件名
print(os.path.dirname(path))        # 目录名
print(os.path.splitext(path))       # 分离扩展名
 
# 环境变量
print(os.environ.get('HOME'))
os.environ['MY_VAR'] = 'value'

10.7.2 sys模块 - 系统相关

import sys
 
print(sys.version)                  # Python版本
print(sys.platform)                 # 平台标识
print(sys.argv)                     # 命令行参数
sys.exit(0)                         # 退出程序
 
# 标准输入输出
sys.stdout.write('Hello\\n')
sys.stderr.write('Error message\\n')

10.7.3 datetime模块 - 日期时间

from datetime import datetime, date, timedelta
import time
 
# 当前时间
now = datetime.now()
print(now.strftime('%Y-%m-%d %H:%M:%S'))
 
# 创建特定日期
d = date(2024, 1, 1)
print(d.weekday())                  # 星期几 (0=周一)
 
# 时间差
delta = timedelta(days=5, hours=3)
future = now + delta
past = now - delta
 
# 时间戳转换
timestamp = time.time()
dt_from_ts = datetime.fromtimestamp(timestamp)

10.7.4 json模块 - JSON处理

import json
 
data = {
    'name': '张三',
    'age': 30,
    'skills': ['Python', 'JavaScript'],
    'address': {
        'city': '北京',
        'zipcode': '100000'
    }
}
 
# 编码为JSON字符串
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
parsed = json.loads(json_str)
 
# 从文件读取
with open('data.json', 'r', encoding='utf-8') as f:
    data_from_file = json.load(f)

10.8 第三方包管理

10.8.1 pip包管理器

pip是Python的标准包管理工具。

# 安装包
pip install requests
pip install numpy pandas matplotlib
 
# 指定版本
pip install requests==2.28.1
pip install 'requests>=2.28.0'
 
# 从requirements.txt安装
pip install -r requirements.txt
 
# 卸载包
pip uninstall requests
 
# 列出已安装包
pip list
pip freeze
 
# 显示包信息
pip show requests

10.8.2 requirements.txt

记录项目依赖及其版本:

requests==2.28.1
numpy>=1.21.0
pandas>=1.3.0
matplotlib>=3.4.0

生成requirements.txt:

pip freeze > requirements.txt

10.9 虚拟环境

虚拟环境用于隔离不同项目的依赖。

10.9.1 venv模块

# 创建虚拟环境
python -m venv myenv
 
# 激活虚拟环境
# Linux/Mac:
source myenv/bin/activate
# Windows:
myenv\\Scripts\\activate
 
# 退出虚拟环境
deactivate

10.9.2 conda环境

# 创建环境
conda create -n myenv python=3.11
 
# 激活环境
conda activate myenv
 
# 退出环境
conda deactivate
 
# 删除环境
conda remove -n myenv --all

10.10 模块发布

将模块打包发布到PyPI供他人使用。

10.10.1 项目结构

mypackage/
├── mypackage/
│   ├── __init__.py
│   └── module.py
├── tests/
│   └── test_module.py
├── setup.py
├── README.md
├── LICENSE
└── MANIFEST.in

10.10.2 setup.py配置

from setuptools import setup, find_packages
 
setup(
    name='mypackage',
    version='1.0.0',
    packages=find_packages(),
    install_requires=[
        'requests>=2.25.0',
        'numpy>=1.20.0',
    ],
    author='Your Name',
    author_email='your@email.com',
    description='A short description',
    long_description=open('README.md').read(),
    long_description_content_type='text/markdown',
    url='https://github.com/username/mypackage',
    classifiers=[
        'Programming Language :: Python :: 3',
        'License :: OSI Approved :: MIT License',
        'Operating System :: OS Independent',
    ],
    python_requires='>=3.8',
)

10.10.3 构建和发布

# 安装构建工具
pip install setuptools wheel twine
 
# 构建包
python setup.py sdist bdist_wheel
 
# 上传到PyPI测试环境
python -m twine upload --repository testpypi dist/*
 
# 上传到正式PyPI
python -m twine upload dist/*

10.11 命名空间包

Python 3.3+支持命名空间包,允许将一个大包分割到多个目录。

# 无需__init__.py的命名空间包
# 项目结构:
# /path1/ns/pkg1.py
# /path2/ns/pkg2.py
 
import sys
sys.path.extend(['/path1', '/path2'])
 
import ns.pkg1
import ns.pkg2

10.12 本章习题

习题1:模块创建

创建一个string_utils.py模块,包含以下函数:

  • reverse_string(s) - 反转字符串
  • count_vowels(s) - 统计元音字母数量
  • is_palindrome(s) - 判断是否为回文
  • word_count(s) - 统计单词数量
  • to_camel_case(s) - 下划线命名转驼峰命名

编写适当的文档字符串和all列表。

习题2:包结构

为一个小型电商系统设计包结构,包含:

  • 用户管理模块(注册、登录、资料)
  • 商品管理模块(商品信息、库存)
  • 订单管理模块(购物车、下单、支付)
  • 工具模块(邮件、短信、日志)

绘制目录结构图并实现基本的init.py文件。

习题3:导入练习

给定以下包结构:

project/
├── app/
│   ├── __init__.py
│   ├── main.py
│   └── config.py
├── utils/
│   ├── __init__.py
│   ├── helpers.py
│   └── validators.py
└── tests/
    └── test_helpers.py

写出在以下情况下正确的导入语句:

1. 在app/main.py中导入utils/helpers.py的函数
2. 在utils/helpers.py中导入app/config.py的配置
3. 在tests/test_helpers.py中导入utils/helpers.py

习题4:实际应用

创建一个命令行工具包cli_tools,包含:

  • cli_tools/file_ops.py - 文件批量处理(重命名、复制、删除)
  • cli_tools/text_ops.py - 文本处理(搜索替换、统计)
  • cli_tools/main.py - 入口点,支持命令行参数

实现功能并确保可以通过“python -m cli_tools”运行。

习题5:虚拟环境实践

创建一个新的虚拟环境,安装以下包并生成requirements.txt:

  • requests
  • beautifulsoup4
  • lxml
  • pillow

编写一个脚本,自动检查并安装requirements.txt中的所有依赖。

10.13 总结

本章学习了:

  • 模块:Python代码的组织单元,通过import语句使用
  • 导入方式:import、from…import、别名、星号导入
  • 模块搜索路径:sys.path控制模块查找位置
  • 特殊属性nameallfile等的用途
  • :层次化的模块组织方式,使用init.py初始化
  • 相对导入与绝对导入:包内部模块间的引用方式
  • 标准库模块:os、sys、datetime、json等常用模块
  • 包管理:pip的使用和requirements.txt
  • 虚拟环境:venv和conda环境管理
  • 模块发布:打包和发布到PyPI的流程

掌握模块和包的使用是编写专业Python代码的基础,良好的代码组织能够显著提高项目的可维护性和协作效率。

python/第十章模块与包.txt · 最后更改: 127.0.0.1