调用一个产生两次的函数

Calling a function, that yields, twice

python3 合作,我有一个要求:

pytest 中的 fixtures 获得灵感,我遇到了 this post 并写了一些疯狂的代码。

虽然这个疯狂的代码有效,但我希望了解使它有效的 yield 魔法:)

def db_connect_n_clean():
  db_connectors = []
  def _inner(db_obj):
    db_connectors.append(db_obj)
    print("Connect : ", db_obj)
  yield _inner
  for conn in db_connectors:
    print("Dispose : ", conn)

这是驱动代码:

pre_worker = db_connect_n_clean()
freaky_function = next(pre_worker)
freaky_function("1")
freaky_function("2")
try:
  next(pre_worker)
except:
  pass

它产生这个输出:

Connect :  1
Connect :  2
Dispose :  1
Dispose :  2
Traceback (most recent call last):
  File "junk.py", line 81, in <module>
    next(pre_worker)
StopIteration

这段代码让我感到困惑的是,对同一个生成器 freaky_func 的所有调用都维护着 db_connectors

的单个列表

在第一个yield之后,所有对象都被处理掉了,我打了StopIteration

我在想调用 freaky_func 两次会维护 2 个单独的列表,并且会有 2 个单独的 yields

Update: 这道题的目的不是想明白怎么实现的。从评论中可以明显看出,context-manager 是要走的路。但我的问题是了解这段代码是如何工作的。基本上,python 的一面。

我最喜欢的 Python 可视化工具之一是 PythonTutor

基本上,您可以在第一个 运行 next(pre_worker) returns 上看到 _inner 函数。由于 _innerdb_connect_n_clean 中,它可以访问它的所有变量。

在内部,在 Python 中,_inner 包含对 db_connectors 的引用。您可以在 __closure__:

下查看参考
>>> gen = db_connect_n_clean()
>>> inner = next(gen)
>>> inner.__closure__
(<cell at 0x000001B73FE6A3E0: list object at 0x000001B73FE87240>,)
>>> inner.__closure__[0].cell_contents
[]

引用的名称与变量相同:

>>> inner.__code__.co_freevars
('db_connectors',)

每次这个特定的函数,这个特定的 __closure__ 尝试访问 db_connectors,它进入同一个列表。

>>> inner(1)
Connect :  1
>>> inner(2)
Connect :  2
>>> inner.__closure__[0].cell_contents
[1, 2]

原来的生成器gen()还在第一个yield暂停:

>>> gen.gi_frame.f_lineno
6  # Gen is stopped at line #6
>>> gen.gi_frame.f_locals["db_connectors"]
[1, 2]

当您使用 next() 再次推进它时,它会从 yield 继续并关闭所有内容:

>>> next(gen)
Dispose :  1
Dispose :  2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

如果您想了解生成器的一般工作原理,可以找到很多关于该主题的答案和文章。例如,我写了 this one

如果我没有完全说明情况,请随时在评论中要求澄清!