为什么 FastAPI 依赖项中产生的 SQLAlchemy 会话一旦超出范围就会关闭?

Why does a yielded SQLAlchemy Session in a FastAPI dependency close once it goes out of scope?

FastAPI docs 中,建议使用生成器函数设置 SQLAlchemy 数据库会话依赖项,如下所示:

async def get_db():
    db = DBSession()
    try:
        yield db
    finally:
        db.close()

我的问题是为什么 finally 块会被执行?我的印象是生成器函数会在每次 yield 时暂停执行。一旦会话对象超出范围,get_db 的执行是否应该被丢弃,db.close() 永远不会有 运行?

如果你在 try 之前在你的依赖中抛出 breakpoint() 并使用 w 选项查看调用树,你可以看到生成器类型函数 next() 被调用在 contextlib 模块代码中:

[0]   c:\users\homeuser\.pyenv\pyenv-win\versions.7.9\lib\threading.py(890)_bootstrap()
-> self._bootstrap_inner()
[1]   c:\users\homeuser\.pyenv\pyenv-win\versions.7.9\lib\threading.py(926)_bootstrap_inner()
-> self.run()
[2]   c:\users\homeuser\.pyenv\pyenv-win\versions.7.9\lib\threading.py(870)run()
-> self._target(*self._args, **self._kwargs)
[3]   c:\users\homeuser\.pyenv\pyenv-win\versions.7.9\lib\concurrent\futures\thread.py(80)_worker()
-> work_item.run()
[4]   c:\users\homeuser\.pyenv\pyenv-win\versions.7.9\lib\concurrent\futures\thread.py(57)run()
-> result = self.fn(*self.args, **self.kwargs)
[5]   c:\users\homeuser\.pyenv\pyenv-win\versions.7.9\lib\contextlib.py(112)__enter__()
-> return next(self.gen)

请注意,此 next() 调用是在 __enter__ 特殊函数内完成的,上下文管理器在使用 with 关键字打开它时使用该特殊函数。现在,如果通过 yield 语句前进并等待,直到您的断点进入 finally 块,您可以在其中再次使用 w 查看调用树:

[0]   c:\users\homeuser\.pyenv\pyenv-win\versions.7.9\lib\threading.py(890)_bootstrap()
-> self._bootstrap_inner()
[1]   c:\users\homeuser\.pyenv\pyenv-win\versions.7.9\lib\threading.py(926)_bootstrap_inner()
-> self.run()
[2]   c:\users\homeuser\.pyenv\pyenv-win\versions.7.9\lib\threading.py(870)run()
-> self._target(*self._args, **self._kwargs)
[3]   c:\users\homeuser\.pyenv\pyenv-win\versions.7.9\lib\concurrent\futures\thread.py(80)_worker()
-> work_item.run()
[4]   c:\users\homeuser\.pyenv\pyenv-win\versions.7.9\lib\concurrent\futures\thread.py(57)run()
-> result = self.fn(*self.args, **self.kwargs)
[5]   c:\users\homeuser\.pyenv\pyenv-win\versions.7.9\lib\contextlib.py(119)__exit__()
-> next(self.gen)

现在您会注意到,当从上下文管理器对象退出时,next() 在您的生成器函数上被调用。

你说的暂停是对的,但功能状态也会被记住。因此,当 __enter__ 中的第一个 next() 被调用时,它会产生您的会话对象,然后第二个 next() 调用恢复函数执行,从而可以进入 finally 块。

我认为 @contextmanager 装饰器在 FastAPI 实现中的某处使用,它负责处理依赖关系,因此可以在 Depends 中使用生成器。 看看 this 文章,它很好地解释了 Python 生成器的工作原理。

devaerial post关于如何在由 FastAPI 生成器依赖项实现的上下文管理器的进入和退出魔术方法期间调用 next() 的一个很好的答案。但是,finally 块执行还有另一个更普遍的原因,我想在此处 post。

我找到了 更广泛的关于发电机的答案。假设我们有一个生成器函数...

def gen():
    yield 1
    print("test")

我的输出将是这样的:

>>> print(next(gen()))
1

但是,如果我使用 try...finally

修改我的函数
def gen():
    try:
        yield 1
    finally:
        print("test")

我会得到这个输出:

>>> print(next(gen()))
test
1

一旦生成器被销毁,finally块保证在垃圾回收之前执行。