← 返回 Day 25 正文

Day 25 · 图解

Day 025 — 上下文管理器 图解

1. with 语句执行流程

flowchart TD subgraph Code["源代码"] C1["with Expression as Target:"] C2[" # 代码块"] end subgraph Step1["Step 1: 获取管理器"] S1["计算 Expression"] S2["获取上下文管理器对象"] end subgraph Step2["Step 2: __enter__"] S3["调用 manager.__enter__()"] S4["返回值绑定到 Target"] end subgraph Step3["Step 3: 执行代码块"] S5["执行 with 块内的代码"] S6{"发生异常?"} end subgraph Step4["Step 4: __exit__"] S7["manager.__exit__(None, None, None)"] S8["manager.__exit__(type, val, tb)"] end subgraph Step5["Step 5: 异常处理"] S9{"__exit__ 返回 True?"} S10["抑制异常,继续执行"] S11["异常继续传播"] end C1 --> S1 C2 -.-> S5 S1 --> S2 S2 --> S3 S3 --> S4 S4 --> S5 S5 --> S6 S6 -- "有异常" --> S8 S6 -- "无异常" --> S7 S8 --> S9 S7 --> E1["正常退出"] S9 -- "True" --> S10 S9 -- "False" --> S11 style S10 fill:#c8e6c9 style S11 fill:#ffcdd2
flowchart TD
    subgraph Code["源代码"]
        C1["with Expression as Target:"]
        C2["    # 代码块"]
    end

    subgraph Step1["Step 1: 获取管理器"]
        S1["计算 Expression"]
        S2["获取上下文管理器对象"]
    end

    subgraph Step2["Step 2: __enter__"]
        S3["调用 manager.__enter__()"]
        S4["返回值绑定到 Target"]
    end

    subgraph Step3["Step 3: 执行代码块"]
        S5["执行 with 块内的代码"]
        S6{"发生异常?"}
    end

    subgraph Step4["Step 4: __exit__"]
        S7["manager.__exit__(None, None, None)"]
        S8["manager.__exit__(type, val, tb)"]
    end

    subgraph Step5["Step 5: 异常处理"]
        S9{"__exit__ 返回 True?"}
        S10["抑制异常,继续执行"]
        S11["异常继续传播"]
    end

    C1 --> S1
    C2 -.-> S5
    S1 --> S2
    S2 --> S3
    S3 --> S4
    S4 --> S5
    S5 --> S6
    S6 -- "有异常" --> S8
    S6 -- "无异常" --> S7
    S8 --> S9
    S7 --> E1["正常退出"]
    S9 -- "True" --> S10
    S9 -- "False" --> S11

    style S10 fill:#c8e6c9
    style S11 fill:#ffcdd2

2. @contextmanager 生成器模式

flowchart TD subgraph Generator["生成器函数"] G1["def my_cm():"] G2[" # setup 代码"] G3[" try:"] G4[" yield VALUE ← __enter__ 返回值"] G5[" finally:"] G6[" # cleanup 代码"] end subgraph WithBlock["with my_cm() as x:"] W1["x = VALUE"] W2["执行 with 块"] end G1 -.-> Setup["__enter__: 执行 yield 之前的代码"] G4 -.-> EnterRet["yield 的值 → as 子句"] G6 -.-> Exit_["__exit__: 执行 finally 块"] Setup --> G2 G2 --> G3 G3 --> G4 G4 --> W1 W1 --> W2 W2 -- "无异常" --> G5 W2 -- "有异常" --> GenErr["异常在 yield 处抛出"] GenErr --> G5 G5 --> G6 style G4 fill:#fff9c4,stroke:#f9a825 style W1 fill:#e3f2fd
flowchart TD
    subgraph Generator["生成器函数"]
        G1["def my_cm():"]
        G2["    # setup 代码"]
        G3["    try:"]
        G4["        yield VALUE  ← __enter__ 返回值"]
        G5["    finally:"]
        G6["        # cleanup 代码"]
    end

    subgraph WithBlock["with my_cm() as x:"]
        W1["x = VALUE"]
        W2["执行 with 块"]
    end

    G1 -.-> Setup["__enter__: 执行 yield 之前的代码"]
    G4 -.-> EnterRet["yield 的值 → as 子句"]
    G6 -.-> Exit_["__exit__: 执行 finally 块"]

    Setup --> G2
    G2 --> G3
    G3 --> G4
    G4 --> W1
    W1 --> W2

    W2 -- "无异常" --> G5
    W2 -- "有异常" --> GenErr["异常在 yield 处抛出"]
    GenErr --> G5
    G5 --> G6

    style G4 fill:#fff9c4,stroke:#f9a825
    style W1 fill:#e3f2fd

3. exit 异常处理决策树

flowchart TD A["with 块内发生异常"] --> B{"__exit__ 返回?"} B -- "True" --> C["✅ 异常被抑制"] B -- "False" --> D["❌ 异常继续传播"] B -- "__exit__ 自身抛出异常 E" --> E["E 替代原异常传播"] C --> F["程序继续执行<br/>with 块之后的代码"] D --> G["原异常传播到外层<br/>try/except 可以捕获"] subgraph Notes["注意事项"] N1["返回 True 要谨慎:<br/>可能掩盖编程错误"] N2["__exit__ 不要轻易抛异常<br/>否则原异常丢失"] N3["通常模式:记录日志后<br/>返回 False"] end style C fill:#c8e6c9 style D fill:#ffcdd2 style E fill:#ffcc80
flowchart TD
    A["with 块内发生异常"] --> B{"__exit__ 返回?"}
    B -- "True" --> C["✅ 异常被抑制"]
    B -- "False" --> D["❌ 异常继续传播"]
    B -- "__exit__ 自身抛出异常 E" --> E["E 替代原异常传播"]

    C --> F["程序继续执行<br/>with 块之后的代码"]
    D --> G["原异常传播到外层<br/>try/except 可以捕获"]

    subgraph Notes["注意事项"]
        N1["返回 True 要谨慎:<br/>可能掩盖编程错误"]
        N2["__exit__ 不要轻易抛异常<br/>否则原异常丢失"]
        N3["通常模式:记录日志后<br/>返回 False"]
    end

    style C fill:#c8e6c9
    style D fill:#ffcdd2
    style E fill:#ffcc80

4. 上下文管理器分层图

┌─────────────────────────────────────────────────────┐
│                 Python 上下文管理器体系                │
├─────────────────────────────────────────────────────┤
│                                                     │
│  ┌─────────────────────────────────────────────┐   │
│  │        核心协议 (PEP 343)                    │   │
│  │  __enter__(self)  → 返回值绑定到 as          │   │
│  │  __exit__(self, exc_type, exc_val, exc_tb)  │   │
│  │     → 返回 bool 决定是否抑制异常              │   │
│  └─────────────────────────────────────────────┘   │
│                                                     │
│  ┌─────────────────────────────────────────────┐   │
│  │       标准库 contextlib 模块                  │   │
│  │                                             │   │
│  │  @contextmanager    生成器方式实现            │   │
│  │  closing(obj)       调用 obj.close()         │   │
│  │  suppress(*excs)    忽略指定异常             │   │
│  │  redirect_stdout    重定向 print 输出        │   │
│  │  redirect_stderr    重定向 stderr            │   │
│  │  ExitStack          动态管理多个管理器       │   │
│  │  nullcontext()      空操作上下文             │   │
│  │  ContextDecorator   装饰器+上下文二合一       │   │
│  └─────────────────────────────────────────────┘   │
│                                                     │
│  ┌─────────────────────────────────────────────┐   │
│  │       内置上下文管理器                        │   │
│  │                                             │   │
│  │  open()            文件操作                  │   │
│  │  threading.Lock    线程锁                   │   │
│  │  subprocess.Popen  子进程                   │   │
│  │  socket.socket     网络套接字               │   │
│  │  decimal.localcontext 十进制上下文          │   │
│  └─────────────────────────────────────────────┘   │
│                                                     │
│  ┌─────────────────────────────────────────────┐   │
│  │       异步上下文管理器 (Python 3.5+)          │   │
│  │  __aenter__(self)                            │   │
│  │  __aexit__(self, exc_type, exc_val, exc_tb)  │   │
│  │  @asynccontextmanager                        │   │
│  │  async with expr as var:                     │   │
│  └─────────────────────────────────────────────┘   │
│                                                     │
└─────────────────────────────────────────────────────┘

5. with 语句的伪代码解释

with EXPRESSION as TARGET:
    BLOCK

等价于:

manager = (EXPRESSION)          # 计算表达式得到管理器
value = manager.__enter__()     # 调用 __enter__
TARGET = value                  # 绑定到 as 目标
exc = True                      # 标记是否有异常
try:
    BLOCK                       # 执行代码块
    exc = False                 # 无异常
except:
    exc = True                  # 有异常
    if not manager.__exit__(*sys.exc_info()):
        raise                   # __exit__ 返回 False,继续传播
finally:
    if not exc:
        manager.__exit__(None, None, None)  # 无异常时正常退出

注意:实际 CPython 实现更复杂(使用特殊的 SETUP_WITH 字节码), 但上述伪代码准确地反映了语义。

6. ExitStack 动态管理架构

flowchart LR subgraph Entry["进入 ExitStack"] E1["stack = ExitStack()"] E2["conn1 = stack.enter_context(open('a.txt'))"] E3["conn2 = stack.enter_context(open('b.txt'))"] E4["conn3 = stack.enter_context(lock)"] end subgraph Exit["退出 with 块"] X1["conn3.close() ← LIFO 顺序"] X2["conn2.close()"] X3["conn1.close()"] end E1 --> E2 --> E3 --> E4 E4 -.-> X1 --> X2 --> X3 style Entry fill:#e8f5e9 style Exit fill:#fce4ec
flowchart LR
    subgraph Entry["进入 ExitStack"]
        E1["stack = ExitStack()"]
        E2["conn1 = stack.enter_context(open('a.txt'))"]
        E3["conn2 = stack.enter_context(open('b.txt'))"]
        E4["conn3 = stack.enter_context(lock)"]
    end

    subgraph Exit["退出 with 块"]
        X1["conn3.close()  ← LIFO 顺序"]
        X2["conn2.close()"]
        X3["conn1.close()"]
    end

    E1 --> E2 --> E3 --> E4
    E4 -.-> X1 --> X2 --> X3

    style Entry fill:#e8f5e9
    style Exit fill:#fce4ec

7. 异常在 exit 中的传播路径

场景 A:正常退出
  with块 ──(无异常)──→ __exit__(None, None, None)
                               │
                               ↓
                        返回 False → 正常继续

场景 B:异常被抑制
  with块 ──(ZeroDivisionError)──→ __exit__(ZeroDivisionError, ..., ...)
                                           │
                                    返回 True
                                           │
                                           ↓
                                    异常被抑制
                                    程序继续

场景 C:异常被传播
  with块 ──(ValueError)──→ __exit__(ValueError, ..., ...)
                                     │
                              返回 False
                                     │
                                     ↓
                              ValueError 继续传播
                              try/except 可以捕获

场景 D:异常被替换
  with块 ──(ValueError)──→ __exit__(ValueError, ..., ...)
                                     │
                              __exit__ 自身抛出 RuntimeError
                                     │
                                     ↓
                              RuntimeError 替代 ValueError
                              原 ValueError 丢失 (__context__ 中)