课程进度 课程大纲 已发布 24/24 课
Python 基础
数据与集合
构建可靠的程序
使用对象建模
专业 Python
高级 Python
把代码放进带标签的抽屉
一个很长的 Python 文件就像一个巨大的玩具箱:所有东西都在里面,但什么都不容易找到。Python 让我们可以为每项工作准备一个带标签的抽屉。
-
模块
一个可导入的
.py文件 - 导入包 一个包含模块的文件夹
- 发行包 pip 可以安装的东西
- 虚拟环境 某个项目专属的 Python 空间
这些名称彼此相关,但不能互换使用。import 加载的是模块或导入包。python -m pip install ... 安装的是一个发行包。一个发行包可以提供多个导入包,而且它的安装名称不一定与导入名称相同。
导入时不要弄乱名称
import statistics 会绑定一个模块对象。点号说明了每个工具来自哪里:
import statistics
minutes = [20, 30, 40]
print(statistics.mean(minutes))
print(statistics.median(minutes))
输出:
30
30
这个模块前缀是一个命名空间。它让你自己的名称 mean 可以与 statistics.mean 同时存在。你也可以只导入需要的内容:
from statistics import mean as average
print(average([10, 20, 30]))
只在别名能让含义更清楚时使用它。避免使用 from statistics import *:通配符会隐藏究竟导入了哪些名称,以及它们替换了哪些原有名称。
在一个 Python 进程中,第一次成功导入模块时,会从上到下运行它的顶层代码。之后的普通导入会复用缓存中的模块。因此,顶层代码大多应该用来定义函数、类和常量,而不是请求输入或启动程序。
- 首次导入 运行顶层定义
- 记住 缓存模块对象
- 主程序保护 只在有意运行时执行应用工作
def main():
print("Study report started.")
if __name__ == "__main__":
main()
当它作为入口文件运行时,__name__ 是 "__main__",所以会显示消息。当它被导入时,__name__ 是模块的导入名称,因此 main() 会变得可用,但不会运行。
给包设置一个统一入口
常规包是一个包含 __init__.py 和相关模块的目录:
project/
├── study_report/
│ ├── __init__.py
│ ├── calculations.py
│ └── __main__.py
└── tests.py
__init__.py 用来标记这个常规包,并在第一次导入包时运行。它可以是空的,也可以重新导出一个精简的公共接口。在包内部,一个开头的点表示“从这个包中导入”:
from .calculations import summarize
__all__ = ["summarize"]
__all__ 说明了预期提供的接口;它并不是保护隐私或安全的屏障。避免循环导入:如果 A 导入 B,而 B 又导入 A,请把共享工作移到更底层的模块中,或者通过更清晰的边界传递值。
-
导入
__init__.py打开 API 入口 - 相对导入 点号会保留包的上下文
-
python -m
运行
__main__.py
请在包含这个包的目录中运行它:
python -m study_report
不要运行 python study_report/__main__.py;这样会把包内文件当作独立脚本处理,并可能破坏相对导入。
构建一个可以运行的学习报告包
创建上面的目录树。把以下内容放入 study_report/calculations.py:
def summarize(sessions):
totals = {}
for topic, minutes in sessions:
totals[topic] = totals.get(topic, 0) + minutes
return sorted(totals.items())
把以下内容放入 study_report/__init__.py:
from .calculations import summarize
__all__ = ["summarize"]
把以下内容放入 study_report/__main__.py:
from . import summarize
def main():
sessions = [("Python", 45), ("Git", 40), ("Python", 30)]
print("Study report")
for topic, minutes in summarize(sessions):
print(f"- {topic}: {minutes} minutes")
if __name__ == "__main__":
main()
最后,把以下内容放入包旁边的 tests.py:
from study_report import summarize
assert summarize([]) == []
assert summarize([("Python", 20), ("Python", 30)]) == [("Python", 50)]
print("Tests passed.")
先运行 python tests.py,再运行 python -m study_report。预期输出:
Tests passed.
Study report
- Git: 40 minutes
- Python: 75 minutes
测试中的导入不会打印报告。这个包对外提供一个稳定的操作,而 __main__.py 负责用户可见的应用行为。
检查来源,并给每个项目一个独立空间
名为 statistics.py、json.py 或 pathlib.py 的本地文件可能会遮蔽真正的库。检查 Python 实际加载了什么:
import statistics
print(statistics.__name__)
print(statistics.__file__)
第二行的结果因计算机而异;请确认它指向预期的库,而不是你的项目。应该修正文件名或目录结构,不要到处修改 sys.path。
在项目根目录中创建一个隔离环境:
python -m venv .venv
source .venv/bin/activate
python -c "import sys; print(sys.prefix != sys.base_prefix)"
Windows PowerShell 使用 .venv\Scripts\Activate.ps1。检查结果应该打印 True。使用 python -m pip install distribution-name 安装需要的发行包,然后使用 deactivate 退出环境。把 .venv/ 添加到 .gitignore,并记录创建环境所需的命令;应当重新创建环境,而不是移动或提交它。venv 隔离的是已安装的发行包,而不是你的数据文件或导入设计。
三个小任务与收尾检查
- 安全导入。 把一个温度转换函数移到
conversions.py;把显示内容放在weather_report.py的主程序保护之后。确认导入它时不会打印任何内容。 - 新建包。 创建一个
reading_log包,其中包含一个公共汇总函数、一个__init__.py和一个__main__.py。使用python -m reading_log运行它。 - 环境证明。 创建
.venv,激活它,确认前缀检查结果为True,停用环境,并确认 Git 会忽略.venv/。
容易踩到的坑:通配符导入会隐藏名称,顶层工作会产生导入副作用,直接执行可能丢失包的上下文,而本地文件名可能导致加载错误的模块。
- 我可以区分模块、导入包、发行包和 venv。
- 我可以解释命名空间和首次导入时的执行行为。
- 我可以使用主程序保护和精简的
__init__.py接口。 - 我可以使用
python -m运行并测试包。 - 我可以检查已导入模块的来源。
- 我可以创建、验证、退出并重新创建
.venv。 - 我完成了三个小任务。
接下来,你将使用 pathlib、with、CSV 和 JSON,为这些组织良好的代码提供安全的本地存储。