Python教程21:包(Package)

“治大国如烹小鲜,理大项目如分包装。”

上一课我们学习了模块,今天更进一步,学习包(Package)——模块的集合。当项目规模变大时,包能帮你更好地组织代码。

1. 什么是包

定义

**包(Package)**是一个包含__init__.py文件的目录,用于组织相关的模块。

为什么需要包

  • 层次化组织:大项目有上百个模块,需要分类
  • 命名空间:不同包可以有同名模块
  • 代码复用:打包分发给他人使用
  • 团队协作:不同团队负责不同包

简单示例

项目结构:

myproject/
├── main.py
└── utils/              # 这是一个包
    ├── __init__.py     # 必需!标识这是一个包
    ├── string_utils.py
    └── math_utils.py

init.py的作用

  • 告诉Python这个目录是一个包
  • 可以为空文件
  • 也可以包含包的初始化代码
  • Python 3.3+可以省略(但不推荐)

使用包:

1# main.py
2from utils import string_utils
3from utils import math_utils
4
5# 或者
6import utils.string_utils
7import utils.math_utils

2. 创建第一个包

步骤

  1. 创建目录结构
mymath/
├── __init__.py
├── basic.py
└── advanced.py
  1. 编写模块代码
 1# mymath/basic.py
 2"""基础数学运算"""
 3
 4def add(a, b):
 5    """加法"""
 6    return a + b
 7
 8def subtract(a, b):
 9    """减法"""
10    return a - b
 1# mymath/advanced.py
 2"""高级数学运算"""
 3
 4def power(base, exp):
 5    """幂运算"""
 6    return base ** exp
 7
 8def sqrt(x):
 9    """平方根(简单实现)"""
10    return x ** 0.5
  1. 配置__init__.py
 1# mymath/__init__.py
 2"""
 3mymath包:提供数学运算功能
 4
 5这个文件在包被导入时执行,可以用来:
 61. 初始化包级别的变量
 72. 导入子模块,简化使用
 83. 定义__all__,控制from package import *的行为
 9"""
10
11# 包级别的变量
12VERSION = "1.0.0"
13
14# 简化导入:用户可以直接from mymath import add
15from .basic import add, subtract
16from .advanced import power, sqrt
17
18# 定义公开接口
19__all__ = ['add', 'subtract', 'power', 'sqrt', 'VERSION']
20
21# 包初始化代码
22print(f"mymath包已加载,版本:{VERSION}")
  1. 使用包
 1# 方式1:直接从包导入(因为__init__.py中重新导出了)
 2from mymath import add, power
 3print(add(1, 2))     # 3
 4print(power(2, 3))   # 8
 5
 6# 方式2:从子模块导入
 7from mymath.basic import add
 8from mymath.advanced import power
 9
10# 方式3:导入整个包
11import mymath
12print(mymath.add(1, 2))
13print(mymath.VERSION)

3. 子包和嵌套结构

包可以包含子包,形成层次结构:

myproject/
├── main.py
└── utils/
    ├── __init__.py
    ├── string/         # 子包
    │   ├── __init__.py
    │   ├── formatter.py
    │   └── validator.py
    └── math/           # 子包
        ├── __init__.py
        ├── basic.py
        └── advanced.py

使用子包:

1# 完整路径导入
2from utils.string.formatter import capitalize
3from utils.math.basic import add
4
5# 或者
6import utils.string.formatter
7utils.string.formatter.capitalize("hello")

命名建议

  • 包名全小写
  • 避免使用下划线(除非必要)
  • 使用有意义的名称

4. 相对导入

在包内部,模块之间可以使用相对导入

绝对导入vs相对导入

mymath/
├── __init__.py
├── basic.py
└── advanced.py  # 需要使用basic.py中的函数

绝对导入(从包根开始):

1# mymath/advanced.py
2from mymath.basic import add  # 绝对导入
3
4def complex_add(a, b, c):
5    """使用basic模块的add函数"""
6    return add(add(a, b), c)

相对导入(使用.和..):

1# mymath/advanced.py
2from .basic import add  # 相对导入:.表示当前包
3
4def complex_add(a, b, c):
5    return add(add(a, b), c)

相对导入语法

  • .:当前包
  • ..:上一级包
  • ...:上上级包(依此类推)
1# 假设在utils/string/formatter.py中
2
3from . import validator          # 同级模块
4from .. import math             # 上级包的math子包
5from ..math import basic        # 上级包的math.basic模块
6from ...config import settings  # 两级以上(少用)

注意

  • 相对导入只能在包内部使用
  • 不能在顶层脚本中使用相对导入
  • 脚本应该用绝对导入

5. __all__的使用

__all__定义了from package import *时导入的内容:

1# mymath/__init__.py
2from .basic import add, subtract
3from .advanced import power, sqrt
4
5# 只导出这些
6__all__ = ['add', 'power']

使用:

1from mymath import *
2
3add(1, 2)    # OK
4power(2, 3)  # OK
5# subtract(5, 3)  # NameError

最佳实践

  • 总是定义__all__
  • 只导出稳定的公开API
  • 避免import *(除了在__init__.py中)

6. 包的分发

基本结构

一个可分发的Python包通常包含:

mypackage/
├── setup.py          # 打包配置(关键)
├── README.md         # 项目说明
├── LICENSE           # 许可证
├── requirements.txt  # 依赖列表
└── mypackage/        # 实际的包
    ├── __init__.py
    ├── module1.py
    └── module2.py

setup.py示例

 1# setup.py
 2"""
 3setup.py是Python包的打包配置文件
 4使用setuptools库(Python打包工具的事实标准)
 5"""
 6
 7from setuptools import setup, find_packages
 8
 9setup(
10    name="mypackage",              # 包名
11    version="1.0.0",               # 版本号
12    author="Your Name",            # 作者
13    author_email="you@example.com",
14    description="A short description",
15    long_description=open("README.md").read(),
16    long_description_content_type="text/markdown",
17    url="https://github.com/yourusername/mypackage",
18    packages=find_packages(),       # 自动找到所有包
19    classifiers=[                   # 分类信息
20        "Programming Language :: Python :: 3",
21        "License :: OSI Approved :: MIT License",
22    ],
23    python_requires=">=3.7",        # Python版本要求
24    install_requires=[              # 依赖包
25        "requests>=2.25.0",
26    ],
27)

打包和安装

1# 打包
2python setup.py sdist bdist_wheel
3
4# 本地安装(开发模式,代码修改立即生效)
5pip install -e .
6
7# 上传到PyPI(Python包索引,pip install的来源)
8twine upload dist/*

PyPI(Python Package Index):

  • Python官方的包仓库
  • pip install package_name从这里下载
  • 任何人都可以上传包

7. 常见包结构模式

单包模式

project/
├── setup.py
├── README.md
└── mypackage/
    ├── __init__.py
    └── core.py

多包模式

project/
├── setup.py
├── README.md
└── myproject/
    ├── __init__.py
    ├── package1/
    │   ├── __init__.py
    │   └── module1.py
    └── package2/
        ├── __init__.py
        └── module2.py

应用程序模式

myapp/
├── setup.py
├── README.md
├── myapp/              # 主包
│   ├── __init__.py
│   ├── cli.py          # 命令行接口
│   ├── api/            # API子包
│   │   └── __init__.py
│   └── utils/          # 工具子包
│       └── __init__.py
├── tests/              # 测试(不在包内)
│   └── test_cli.py
└── docs/               # 文档
    └── index.md

8. 命名空间包(Python 3.3+)

命名空间包允许一个包分散在多个目录中,不需要__init__.py

# 目录1
site-packages/
└── myns/
    └── sub1/
        └── module1.py

# 目录2
site-packages2/
└── myns/
    └── sub2/
        └── module2.py

使用:

1# 两个目录的myns会合并成一个命名空间
2from myns.sub1 import module1
3from myns.sub2 import module2

使用场景

  • 插件系统
  • 大型项目拆分
  • 多团队协作

9. 小结

今天我们学习了Python包:

  • 定义:包含__init__.py的目录
  • 结构:可以嵌套子包
  • 导入:绝对导入vs相对导入
  • init.py:包初始化、重新导出、定义__all__
  • 分发:setup.py、PyPI
  • 模式:单包、多包、应用程序
  • 命名空间包:高级特性

包是Python项目组织的核心,掌握它能让你的项目更专业、更易维护。


练习题

  1. 创建一个包含3个子模块的包,练习相对导入
  2. 为你的包编写setup.py,尝试本地安装
  3. 探索一个知名Python包的结构(如requests)

本文代码示例

关注公众号:极客老墨

更多 AI 应用开发、工程实践和效率工具分享,欢迎扫码关注。

极客老墨微信公众号二维码

相关阅读