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. 创建第一个包
步骤
- 创建目录结构:
mymath/
├── __init__.py
├── basic.py
└── advanced.py
- 编写模块代码:
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
- 配置__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:直接从包导入(因为__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项目组织的核心,掌握它能让你的项目更专业、更易维护。
练习题:
- 创建一个包含3个子模块的包,练习相对导入
- 为你的包编写setup.py,尝试本地安装
- 探索一个知名Python包的结构(如requests)
本文代码示例:
关注公众号:极客老墨
更多 AI 应用开发、工程实践和效率工具分享,欢迎扫码关注。
