调用一个产生两次的函数
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
函数。由于 _inner
在 db_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。
如果我没有完全说明情况,请随时在评论中要求澄清!
与 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
函数。由于 _inner
在 db_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。
如果我没有完全说明情况,请随时在评论中要求澄清!