为什么 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
块保证在垃圾回收之前执行。
在 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
块保证在垃圾回收之前执行。