Python 语法与工程骨架
想在大型项目里写出“既优雅又不掉坑”的 Python?本文用 抽象基类 → 生成器 → 类型提示 → 模块组织 四步,给你一套可复用的工程骨架。
📚 省流图
主题 | 一句话记忆 |
---|---|
抽象基类 | from abc import ABC, abstractmethod → 逼子类覆写接口 |
生成器 | yield → 一次返回多批结果,for 自动迭代 |
类型提示 | Tuple[int, ...] 读作“不可变整型序列” |
模块路径 | package.sub.module → 对应 package/sub/module.py |
1️⃣ 面向对象:ABC、继承、重写
1 | from abc import ABC, abstractmethod |
- ABC:
AbsSolver
继承自ABC
,内部 至少有一个@abstractmethod
。 - 强制实现:实例化子类前,Python 会检查是否实现了所有抽象方法;否则
TypeError
。 - 可选覆写:
dump
不是抽象的,子类可按需扩展。
@abstractmethod
用法速览
1 | class Base(ABC): |
省流:ABC = “接口”,“抽象方法” = “必须实现”。
🔗 什么是“钩子 (Hook)”?
- 非抽象、带默认实现的方法,子类可选地覆写。
- 框架在合适时机回调它,让开发者“插入自定义逻辑”。
- 例:
dump()
就是调试钩子,PyTorch 的forward_hook
同理。
🦆 什么是“鸭子接口 / Duck Typing”?
- 不关心对象真实类型,只要 行为/look like a duck 就当成合法。
ops.py
用pass
而非@abstractmethod
,就是“鸭子接口”:谁实现了同名方法谁就能用。
- 优点:灵活、少耦合;缺点:漏实现时运行期才炸。
记忆:Hook = 可选扩展点;Duck Typing = 不验血统,只看会不会叫、会不会游。
2️⃣ 生成器与迭代:持续产出多解
在 ops.py
里,materialize()
用 yield
按需吐出 不同框架的层对象:
1 | class AbsBaseOp(ABC): |
yield
:函数变生成器,for layer in op.materialize('torch'):
可逐层迭代。- 多解采样:在循环内 动态给求解器加“不等”约束,驱动 Z3 返回新模型。
- 终止信号:最后
yield None
让上层知道“解完了”。
yield from
简洁重用
1 | def pipeline(frameworks): |
🏃 如何读取 / 消费生成器?
- for-loop 最省心
1
2
3
4for layer in op.materialize('torch'):
if layer is None:
break # 无更多解
print(layer) next()
手动拉取(需要捕获StopIteration
)1
2
3
4
5
6
7
8
9g = op.materialize('paddle')
try:
while True:
layer = next(g)
if layer is None:
break
use(layer)
except StopIteration:
pass- 切片取前 N 个:
import itertools; first5 = list(itertools.islice(op.materialize('tf'), 5))
记忆:
yield
负责“产”,for / next / islice
负责“取”。
3️⃣ 类型提示速读
写法 | 读法 | 示例 |
---|---|---|
List[int] |
可变 整型列表 | [1, 2, 3] |
Tuple[str, int] |
长度固定 (str, int) |
("id", 3) |
Tuple[int, ...] |
不定长整型元组 | (1,2,3,4) |
Dict[str, Any] |
key 是 str, value 任意 | {"a":1} |
Union[int, str] |
int 或 str | 42 / "42" |
Optional[Foo] |
Union[Foo, None] |
可能返回 None |
检查工具:
mypy
,pyright
,CI 一跑便知类型是否对。
4️⃣ 模块组织与导入路径
1 | project/ |
- 相对导入:包内部用
from .base import AbsBaseOp
,防止顶层路径污染。 - 绝对导入:外部调用写
from ops.nn import ConvOp
,IDE/类型检查友好。 __init__.py
:在包级暴露高层 API,隐藏实现细节。
flowchart TD
subgraph "ops 包"
base["base.py\nAbsBaseOp"]
nn["nn.py\nConvOp / PoolOp"]
shape["shape.py\nZ3 求解"]
end
scripts["scripts/train.py"] --> nn
scripts --> base
nn --> shape
📝 小结
- ABC + 抽象方法 → 先定接口,再写实现。
- 生成器 (
yield
) → 惰性产出多解/多批数据,节省内存。 - Typing →
Tuple[int, ...]
=不定长;Union
=多选;配合 IDE CI 早点发现错。 - 模块化 → “包内相对、包外绝对”,在
__init__.py
只输出你想给用户看的接口。
熟练掌握这四招,你的 Python 项目就能又 清晰 又 可维护 🚀