(八)把函数放进模块
1. 导入文件与访问名称
前面的函数都写在同一个文件里。现在准备让菜单与统计工具共同使用合计函数,可以把它单独放进 stats.py。下面两份文件保存在同一个 module_demo 文件夹中:
# 项目文件:module_demo/stats.py
# 这行输出用来观察模块何时执行;普通工具模块可以只保留定义。
print("载入 stats")
GOAL = 30
def total_minutes(values):
"""合计交入的分钟数。"""
return sum(values)
def main():
# 自检集中在入口函数中,导入时不会直接调用它。
print("模块自检", total_minutes([20, 35]))
if __name__ == "__main__":
main()stats.py 这样的 Python 文件可以作为模块。模块内部保存自己的名称,例如 GOAL、total_minutes。接着建立调用文件:
# 项目文件:module_demo/app.py
# import 先取得模块对象,再把名称 stats 绑定到它。
import stats
import stats
from stats import GOAL
print(stats.__name__)
print(stats.total_minutes([20, 35]))
# from 导入建立当前文件自己的绑定;后续重新绑定模块属性不会追着更新它。
stats.GOAL = 45
print(GOAL, stats.GOAL)在 module_demo 文件夹打开终端:
# app.py 通过相邻的 stats.py 获取统计函数。
python app.py
输出:
载入 stats
stats
55
30 45
import stats 后用 stats.total_minutes 访问名称;from stats import GOAL 则把找到的对象绑定到当前文件的 GOAL。需要给模块起短名时,可以写 import stats as reading_stats,随后用 reading_stats.total_minutes(...)。
最后一行中的两个名称起初都指向整数 30。重新给 stats.GOAL 绑定 45 后,本文件的 GOAL 仍指向 30。若导入的是同一个列表,原地修改列表时,仍遵循前面讲过的对象共享规则。模块导入与名称
2. 首次导入、缓存与 __name__
第一次导入 stats 时,解释器建立模块对象,先把它登记到 sys.modules,再执行顶层语句,创建其中的函数与其他名称。第二次导入找到已有记录,因此只看到一次“载入 stats”。这是同一个解释器进程内的缓存;重新启动一次程序,又会从首次导入开始。
__name__ 是模块中的名称。通过 import stats 载入时,它是 "stats";直接执行 python stats.py 时,它是 "__main__"。因此这个判断把“导入可复用函数”和“直接运行入口”分开:
| 启动动作 | stats.py 中的 __name__ |
是否调用 main() |
可见输出 |
|---|---|---|---|
python app.py |
"stats" |
否 | “载入 stats”后由 app.py 输出结果 |
python stats.py |
"__main__" |
是 | “载入 stats”和“模块自检 55” |
if __name__ == "__main__": 不会阻止其他顶层语句执行,所以观察用的 print("载入 stats") 仍然出现。实际工具模块把读输入、连接服务等动作放进明确调用的函数中,导入者就能先拿到工具,再决定何时执行这些动作。顶层入口说明
3. 查找位置与导入依赖
运行脚本时,脚本所在目录会参与模块查找;使用 python -m ... 时,当前目录参与查找。解释器还会搜索标准库和环境的包目录。sys.path 保存这些查找位置,可以用第一章介绍的 import sys 后查看。
因此,把自己的文件命名为 re.py 或 datetime.py,再导入同名标准库,就可能先找到自己的文件。为项目文件起能表达职责的名称,例如 reading_rules.py,查找结果会更直观。
当 a.py 导入 b.py,而 b.py 又立即从 a.py 读取一个尚未定义的函数时,a 还停在执行导入语句的位置,函数名称没有准备好。可以把双方共享的计算放到 core.py:a、b 分别导入 core,让依赖顺着同一方向走。仅仅把一份长代码分成多个文件,还需要同时整理“谁调用谁”。Python 导入系统
模块还可以用 __all__ 列出准备交给 from stats import * 的公开名称。例如在 stats.py 中写 __all__ = ["total_minutes"],星号导入会按这份名称列表读取;没有这份列表时,星号导入通常选取不以下划线开头的名称。直接写 from stats import GOAL 仍然明确请求 GOAL,__all__ 不承担访问权限检查。
阅读项目代码时,显式写出导入名称更容易知道每个工具来自哪里。包的初始化文件也可以用同样方式整理公开入口,下一节会先建立这个文件与包的关系。模块的公开名称