关于python:在@contextmanager和“with”语句中显式调用__enter__之间的原因不明

Unexplained difference between explicitly invoking __enter__ on @contextmanager vs “with” statement

我有一个上下文管理器类型(Connection)和一个@contextmanager修饰函数,它从with语句中产生该类型。

如果我在装饰函数上显式调用__enter__,则在返回之前在Connection上调用__exit__

这是代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
from __future__ import print_function
from contextlib import contextmanager


class Connection(object):
    def __init__(self):
        self.closed = False

    def __enter__(self):
        print('Connection.__enter__')
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('Connection.__exit__')
        self.closed = True
        return False

    def __repr__(self):
        return"{}(closed={})".format(self.__class__.__name__, self.closed)


@contextmanager
def connect():
    with Connection() as c:
        print('connect yielding connection')
        yield c
        print('connect yielded connection')


def context():
    print("connect explicit call to __enter__")
    c = connect().__enter__()
    print("got connection via __enter__", c)
    print()

    print("connect implicit call to __enter__ via with")
    with connect() as c:
        print("got connection in 'with'", c)
    print("connection after 'with'", c)
    print()

    print("Connection explicit call to __enter__")
    c = Connection().__enter__()
    print("got connection via __enter__", c)
    print()

    print("Connection implicit call to __enter__ via with")
    with Connection() as c:
        print("got connection in with", c)
    print("connection after 'with'", c)


if __name__ =="__main__":
    context()

运行时输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
connect explicit call to __enter__
Connection.__enter__
connect yielding connection
Connection.__exit__
got connection via __enter__ Connection(closed=True)

connect implicit call to __enter__ via with
Connection.__enter__
connect yielding connection
got connection in 'with' Connection(closed=False)
connect yielded connection
Connection.__exit__
connection after 'with' Connection(closed=True)

Connection explicit call to __enter__
Connection.__enter__
got connection via __enter__ Connection(closed=False)

Connection implicit call to __enter__ via with
Connection.__enter__
got connection in with Connection(closed=False)
Connection.__exit__
connection after 'with' Connection(closed=True)

将以"connect explicit call to __enter __"开头的序列与"将_隐式调用连接到__enter__"进行比较。

当我在@contextmanager修饰函数上显式调用__enter__时,为什么在连接返回给我之前调用Connection.__exit__? 为什么"连接产生的连接"从未打印过 - 所以这意味着它仍处于yield语句并且还没有离开with块 - 那么为什么要调用__exit__


您放弃了上下文管理器,因此生成器对象符合回收条件。 生成器的__del__ *自动调用生成器上的close,它在最后yield的点处引发GeneratorExit,结束with Connection() as c块并触发c.__exit__

*从技术上讲,在Python 2上,这实际上发生在比__del__更低级别的清理例程中。