Python教程21:包(Package)
“治大国如烹小鲜,理大项目如分包装。”
上一课我们学习了模块,今天更进一步,学习包(Package)——模块的集合。当项目规模变大时,包能帮你更好地组织代码。
1. 什么是包
定义
**包(Package)**是一个包含__init__.py文件的目录,用于组织相关的模块。
为什么需要包:
- 层次化组织:大项目有上百个模块,需要分类
- 命名空间:不同包可以有同名模块
- 代码复用:打包分发给他人使用
- 团队协作:不同团队负责不同包
简单示例
项目结构:
1myproject/
2├── main.py
3└── utils/ # 这是一个包
4 ├── __init__.py # 必需!标识这是一个包
5 ├── string_utils.py
6 └── 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. 创建第一个包
步骤
- 创建目录结构:
1mymath/
2├── __init__.py
3├── basic.py
4└── 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. 子包和嵌套结构
包可以包含子包,形成层次结构:
1myproject/
2├── main.py
3└── utils/
4 ├── __init__.py
5 ├── string/ # 子包
6 │ ├── __init__.py
7 │ ├── formatter.py
8 │ └── validator.py
9 └── math/ # 子包
10 ├── __init__.py
11 ├── basic.py
12 └── 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相对导入
1mymath/
2├── __init__.py
3├── basic.py
4└── 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包通常包含:
1mypackage/
2├── setup.py # 打包配置(关键)
3├── README.md # 项目说明
4├── LICENSE # 许可证
5├── requirements.txt # 依赖列表
6└── mypackage/ # 实际的包
7 ├── __init__.py
8 ├── module1.py
9 └── 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. 常见包结构模式
单包模式
1project/
2├── setup.py
3├── README.md
4└── mypackage/
5 ├── __init__.py
6 └── core.py
多包模式
1project/
2├── setup.py
3├── README.md
4└── myproject/
5 ├── __init__.py
6 ├── package1/
7 │ ├── __init__.py
8 │ └── module1.py
9 └── package2/
10 ├── __init__.py
11 └── module2.py
应用程序模式
1myapp/
2├── setup.py
3├── README.md
4├── myapp/ # 主包
5│ ├── __init__.py
6│ ├── cli.py # 命令行接口
7│ ├── api/ # API子包
8│ │ └── __init__.py
9│ └── utils/ # 工具子包
10│ └── __init__.py
11├── tests/ # 测试(不在包内)
12│ └── test_cli.py
13└── docs/ # 文档
14 └── index.md
8. 命名空间包(Python 3.3+)
命名空间包允许一个包分散在多个目录中,不需要__init__.py:
1# 目录1
2site-packages/
3└── myns/
4 └── sub1/
5 └── module1.py
6
7# 目录2
8site-packages2/
9└── myns/
10 └── sub2/
11 └── 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 应用开发、工程实践和效率工具分享,欢迎扫码关注。
