PyPyBridge

从 if err != nil 到 try/except

异常是 Python 的主流错误通道。panic/recover 对应 raise/except 顶层兜底,%w 包装对应 raise from。

12 分钟 · 右栏代码可点击「运行」


同一个错误传播

Go
1func load(path string) ([]byte, error) {
2 data, err := os.ReadFile(path)
3 if err != nil {
4 return nil, fmt.Errorf(
5 "load %q: %w", path, err)
6 }
7 return data, nil
8}
9
10// 调用处
11data, err := load("cfg.toml")
12if err != nil {
13 log.Fatal(err)
14}
Python
1def load(path: str) -> bytes:
2 try:
3 return open(path, "rb").read()
4 except OSError as e:
5 raise RuntimeError(
6 f"load {path!r}: {e}") from e
7
8# 调用处
9try:
10 data = load("cfg.toml")
11except RuntimeError as e:
12 print("fatal:", e)
  • fmt.Errorf(..., %w)raise X(...) from e,异常链都会保留原因;errors.Is/As → 多层 except SpecificError
  • 函数签名不再返回 error —— 失败通道挪到了异常,返回值就是真返回值。

自定义错误 → 自定义异常

Go
1type ValidationError struct {
2 Field string
3}
4
5func (e *ValidationError) Error() string {
6 return "bad field: " + e.Field
7}
8
9if bad {
10 return &ValidationError{Field: "age"}
11}
Python
1class ValidationError(Exception):
2 def __init__(self, field: str):
3 super().__init__(f"bad field: {field}")
4 self.field = field
5
6if bad:
7 raise ValidationError("age")

注意

不要把 Go 的习惯带进来在 Python 里写 def load(path) -> tuple[bytes, str | None] 这种 (result, err) 返回值会被认为是坏味道。错误走异常,正常值走返回值,二者不混。panic/recover 在 Python 里的对应物是「顶层 except 兜底记录日志」,而不是常规控制流。

TIP

EAFP 会在你身上发生写惯 if ok, err := ... 之后,Python 的 try: return cache[k] / except KeyError: 会先让你不安,然后让你上瘾 —— 检查与使用之间的竞态窗口消失了。

练一练

这张卡的示例代码已在游乐场中备好,改一改再运行。

在游乐场中打开