Python3 with

with 语句的语法、上下文管理器协议、典型应用场景与自定义实现。

Intermediate
Key Pointswith 保证退出时一定执行清理,等价于 try/finally 但更简洁;协议是 __enter__ 与 __exit__。

为什么需要 with 语句?

Files, locks and connections must be released in pairs. try/finally guarantees it but is boilerplate-heavy and easy to forget. with compresses acquire → use → release into one block.

Python
f = open('demo.txt', 'w', encoding='utf-8')
try:
    f.write("hello")
finally:
    f.close()
print("closed:", f.closed)

with open('demo.txt', 'w', encoding='utf-8') as g:
    g.write("hello")
print("closed:", g.closed)
Output
closed: True
closed: True

with 语句的基本语法

with expr as var: — the expression yields a context manager and as binds whatever __enter__ returns. Several resources may be managed at once, comma-separated.

Python
with open('a.txt', 'w', encoding='utf-8') as a, \
     open('b.txt', 'w', encoding='utf-8') as b:
    a.write("A")
    b.write("B")

print(open('a.txt', encoding='utf-8').read())
print(open('b.txt', encoding='utf-8').read())
print(a.closed, b.closed)
Output
A
B
True True

with 语句的工作原理

Entering calls __enter__; leaving calls __exit__(exc_type, exc, tb). If the block raised, returning True from __exit__ suppresses the exception; False or None lets it propagate.

Python
class Trace:
    def __enter__(self):
        print("enter")
        return self

    def __exit__(self, et, e, tb):
        print("exit", et.__name__ if et else None)
        return False

with Trace():
    print("body")

try:
    with Trace():
        raise ValueError("boom")
except ValueError:
    print("propagated")

class Swallow(Trace):
    def __exit__(self, et, e, tb):
        print("swallowed", et.__name__)
        return True

with Swallow():
    raise ValueError("hidden")
print("after")
Output
enter
body
exit None
enter
exit ValueError
propagated
enter
swallowed ValueError
after

实际应用场景

The three common cases: file I/O, thread/process locks, and database transactions or connections. All share the need for paired release even on exceptions.

Python
import threading
import tempfile
import os

lock = threading.Lock()
with lock:
    print("lock held:", lock.locked())
print("lock released:", lock.locked())

with tempfile.TemporaryDirectory() as d:
    path = os.path.join(d, 'x.txt')
    with open(path, 'w', encoding='utf-8') as f:
        f.write("data")
    print("wrote:", os.path.exists(path))
print("dir removed:", os.path.exists(d))
Output
lock held: True
lock released: False
wrote: True
dir removed: False

创建自定义上下文管理器

Two ways: a class with __enter__/__exit__, or decorate a generator with contextlib.contextmanager — the latter is shorter: code before yield is setup, after it is teardown.

Python
from contextlib import contextmanager
import time

@contextmanager
def timer(label):
    start = time.perf_counter()
    try:
        yield label
    finally:
        print(label, "took > 0s:", time.perf_counter() - start >= 0)

with timer("work") as name:
    total = sum(range(1000))

print(name, total)
Output
work took > 0s: True
work 499500

Friends & Partners