with 语句是什么

with 是 Python 的上下文管理器语法,核心思想是:把”进入某个环境”和”离开时的清理工作”封装在一起,保证无论是否发生异常,清理逻辑都会被执行。

# 不用 with
f = open("file.txt")
data = f.read()
f.close()  # 若上方抛异常,这行永远不会执行
 
# 用 with
with open("file.txt") as f:
    data = f.read()
# 离开块时,f.close() 自动调用

底层机制

with 依赖两个魔术方法:

class MyContext:
    def __enter__(self):
        return self  # 绑定到 as 后面的变量
 
    def __exit__(self, exc_type, exc_val, exc_tb):
        # 无论是否异常都会执行
        # 返回 True 表示吞掉异常,False/None 表示继续传播
        return False

with 语句本质等价于:

f = open("file.txt")
try:
    data = f.read()
finally:
    f.__exit__(...)  # 即 f.close()

魔法不在 with,而在 finally 的语义保证 + 文件对象自己在 __exit__ 里写了 close()

contextlib 简化写法

不想定义类时,用 @contextmanager 装饰器:

from contextlib import contextmanager
 
@contextmanager
def my_context():
    print("进入")
    try:
        yield "资源对象"  # yield 前 = __enter__,yield 后 = __exit__
    finally:
        print("离开")

yield 把函数劈成两半,前半是 setup,后半是 teardown。

魔术方法

Python 里以双下划线开头和结尾的方法,如 __init____enter____len__。“魔术”在于:你不直接调用它们,Python 在特定语法触发时替你调用。

语法实际调用
with objobj.__enter__() / obj.__exit__()
for x in objobj.__iter__() / obj.__next__()
len(obj)obj.__len__()
obj[key]obj.__getitem__(key)
obj + otherobj.__add__(other)

可以把魔术方法理解为 Python 的隐式接口——接口名不是你定义的,是语言约定死的。

与 Go 接口的对比

同一个需求:“能发出声音的东西”。

Go:先定义接口,类型隐式满足

type Speaker interface {
    Speak() string
}
 
type Dog struct{}
func (d Dog) Speak() string { return "Woof" }
 
func MakeNoise(s Speaker) {
    fmt.Println(s.Speak())
}

Python:靠约定的魔术方法,duck typing

class Dog:
    def __str__(self):
        return "Woof"
 
def make_noise(animal):
    print(str(animal))  # 调用 animal.__str__()

核心差异:

  • Go 对接口编程,编译期检查,MakeNoise 明确要求 Speaker
  • Python 的 make_noise 对参数类型无要求,只要有 __str__ 就能跑——这是鸭子类型:能叫就是鸭子。
  • 代价是错误从编译期推迟到运行时。

两者思路相近——“有这个方法就能用”——只是切入点不同。Go 是显式接口声明,Python 是隐式魔术方法协议。