Python 程序结构
Python 以模块为基本编译单元:语句顺序执行、导入绑定名字,以及用 __name__ 区分库与入口脚本。
#concept
#growing
#dev
#python
#python
[!info] related notes
Python 程序结构
这个概念为什么出现
很多语言有 main 函数硬入口。Python 更常是:一个 .py 文件就是一个模块,从上到下执行语句;当你“运行脚本”时,这个模块的名字被设为 "__main__"。不理解这一点,会出现:
- 写库时顶层代码乱执行
- 循环导入时初始化顺序失控
- 测试时无法 import 业务函数
[!abstract] 一句话理解 Python 程序由模块组成:普通导入复用 sys.modules 中的模块;首次加载时先缓存模块对象,再执行模块体。脚本或 -m 入口以
__main__运行,可用名称守卫隔离 CLI 行为。
最小可运行示例
先把示例放进业务场景,再看代码:
场景:订单号工具既要被 Web 层 import,也能命令行自测
仓储系统需要 normalize_order_id:去掉空格、转大写。Web handler 会 import 它;运维也想 python order_id.py ORD-1 快速试。
# order_id.py
# 业务意图:提供可复用的订单号规范化,并保留 CLI 自测入口。
# 教学点:
# - 模块导入时会执行顶层 def(绑定函数对象),但不应有“打印/连库”副作用;
# - 仅当 __name__ == "__main__" 时解析命令行并打印。
import sys
def normalize_order_id(raw: str) -> str:
# 业务规则:首尾空白去掉,统一大写,保证检索键一致。
return raw.strip().upper()
if __name__ == "__main__":
# 脚本方式或 python -m order_id 作为入口运行时都会走这里。
if len(sys.argv) != 2:
print("usage: python order_id.py <order-id>", file=sys.stderr)
raise SystemExit(2)
print(normalize_order_id(sys.argv[1]))
建议运行:
python order_id.py " ab-100 "
python -c "import order_id; print(order_id.normalize_order_id('x-1'))"
期望输出:
AB-100
X-1
结合场景再看三个关注点
- import 不进
__main__分支,所以 Web 层 import 不会误解析sys.argv。 - 顶层适合:import、常量、函数/类定义;不适合:连数据库、读生产密钥。
- 包项目更推荐
python -m package.module作为入口约定。
核心概念与准确模型
1. 模块是基本单位
- .py 是常见模块来源,也有内建模块、扩展模块和 namespace package;同一文件以不同模块名加载,可能产生不同对象。
- 普通首次导入先缓存模块对象再执行模块体;同名再次导入通常复用缓存。reload、删缓存或另一个模块名不属于“只执行一次”的保证。
2. 名字空间
- 模块有自己的全局名字空间。
import order_id在当前模块绑定名字order_id指向该模块对象。from order_id import normalize_order_id绑定的是函数对象本身。
3. __name__ 规则
| 加载方式 | 典型 __name__ |
|---|---|
python order_id.py | "__main__" |
import order_id | "order_id" |
python -m order_id | "__main__"(以模块方式作入口) |
4. 包(package)预告
含 __init__.py(正规包)或命名空间包的目录可组成包;深入见阶段五导入笔记。阶段一只需要:import package.module。
边界情况与反直觉行为
- 循环导入可能访问半初始化模块,导致 AttributeError 或 ImportError;循环本身并非必然失败,关键是何时读取尚未绑定的名字。
- 脚本目录 vs 包目录:直接跑深层文件时,相对导入可能失败;优先模块入口。
python -c与 REPL:也有独立__main__语义,适合临时验证,不适合正式入口。
常见误区
[!warning] 常见误区:把 Python 想成必须有 main 函数 错误理解:没有
def main就不是程序。
正确模型:入口是模块执行 + 可选__main__守卫;def main()只是工程习惯。
工程实践
- 库模块避免启动服务、连接数据库等顶层操作;def 的默认参数和装饰器、class 的类体仍会在定义时执行,
__main__守卫只保护放在该分支内的代码。 - 应用入口:薄
__main__.py或if __name__,内部调用main()。 - 可测试性:业务函数与 IO/CLI 解析分离。
本节总结
程序结构的第一课是 模块执行模型,不是语法花样。把“可被 import 的库”和“可运行的入口”分开,是后续工程化的地基。
自测题
- 为什么测试文件
import order_id不会执行 CLI 分支? - 顶层
print("loaded")在多次 import 时打印几次?
参考答案
- 因为 import 时
__name__是模块名而非"__main__"。 - 通常一次:首次导入执行并缓存;同进程再次 import 不重跑顶层。
延伸阅读与资料来源
| 资料 | 类型 | 支撑内容 |
|---|---|---|
| Tutorial – Modules | 文档 | 模块与 __name__ |
| Reference – Top-level components | 规范 | 程序组成 |