我正在编写一个基于MPI的应用程序(但是MPI在我的问题中并不重要,我只提到它只是为了揭示其原理),在某些情况下,当工作项少于流程时,我需要创建一个新的沟通者,不包括与工作无关的流程。最后,新的沟通者必须由有工作要做的流程释放出来(并且只有它们自己才能完成)。
一种简洁的方法是写:
with filter_comm(comm, nworkitems) as newcomm:
... do work with communicator newcomm...
主体仅由有工作要做的过程执行。
上下文管理器中是否有一种方法可以避免执行主体?我知道上下文管理器的设计应避免隐藏控制流,但是我想知道是否有可能规避这一点,因为在我看来,为清晰起见,这是合理的。
已经提出了有条件跳过上下文管理器主体的功能,但是PEP 377中记录的功能被拒绝了。
我对替代品进行了一些研究。这是我的发现。
[首先让我解释一下我的代码示例的背景。假设您有一堆要使用的设备。对于每个设备,您都必须获取该设备的驱动程序;然后使用驱动程序与设备一起工作;最后释放驱动程序,以便其他人可以获取驱动程序并使用该设备。
这里没有什么不寻常的。该代码大致如下所示:
driver = getdriver(somedevice)
try:
dowork(driver)
finally:
releasedriver(driver)
但是每个月圆一次,如果行星未正确对准,则所获得的设备驱动器不好,该设备无法进行任何工作。这没什么大不了的。只需跳过此轮设备,然后在下一轮再试一次即可。通常情况下,驾驶员是好的。但是,即使是不良的驱动程序也需要释放,否则无法获取新的驱动程序。
((固件是专有的,供应商不愿修复或什至不承认此错误)
代码现在看起来像这样:
driver, isgood = getdriver(somedevice)
try:
if isgood:
dowork(driver)
else:
pass # do nothing or the log error
finally:
release(driver)
这是很多样板代码,每次需要使用设备完成工作时都需要重复。 python with
statement的首选。像这样的东西:
with
然后使用设备时的代码又简短又甜蜜:
# note: this code example does not work
@contextlib.contextmanager
def withgetdriver(devicename):
driver, isgood = getdriver(devicename)
try:
if isgood:
yield driver
else:
pass # do nothing or the log error
finally:
release(driver)
但是这不起作用。因为# note: this code example does not work
with withgetdriver(somedevice) as driver:
dowork(driver)
必须屈服。它可能不会屈服。不屈服会导致context manager升高RuntimeException
。
所以我们必须从上下文管理器中取出测试
contextmanager
并放在@contextlib.contextmanager
def withgetdriver(devicename):
driver, isgood = getdriver(devicename)
try:
yield driver, isgood
finally:
release(driver)
语句的主体中
with
这很丑,因为现在我们再次有了一些样板,每次我们要使用设备时都需要重复。
因此,我们需要一个可以有条件地执行主体的上下文管理器。但是我们没有,因为拒绝了PEP377(建议使用此功能)。谢谢你没有吉多。 (实际上感谢guido提供了强大的python语言,但是我发现这个特殊的决定值得怀疑)
我发现滥用with withgetdriver(somedevice) as (driver, isgood):
if isgood:
dowork(driver)
else:
pass # do nothing or the log error
可以很好地替代可以跳过正文的上下文管理器
generator
但是随后的调用代码看起来非常像一个循环
def generatorgetdriver(devicename):
driver, isgood = getdriver(devicename)
try:
if isgood:
yield driver
else:
pass # do nothing or the log error
finally:
release(driver)
如果可以忍受(请不要),那么您将拥有一个可以有条件地执行尸体的上下文管理器。
似乎防止样板代码的唯一方法是使用回调
for driver in generatorgetdriver(somedevice):
dowork(driver)
和调用代码
def workwithdriver(devicename, callback):
driver, isgood = getdriver(devicename)
try:
if isgood:
callback(driver)
else:
pass # do nothing or the log error
finally:
release(driver)
此功能似乎是workwithdriver(devicename, dowork)
。 Python开发人员通常更喜欢显式变体:
rejected
您也可以使用高阶函数:
if need_more_workers():
newcomm = get_new_comm(comm)
# ...
类似这样的事情如何:
def filter_comm(comm, nworkitems, callback):
if foo:
callback(get_new_comm())
# ...
some_local_var = 5
def do_work_with_newcomm(newcomm):
# we can access the local scope here
filter_comm(comm, nworkitems, do_work_with_newcomm)
您实现@filter_comm(comm, nworkitems)
def _(newcomm): # Name is unimportant - we'll never reference this by name.
... do work with communicator newcomm...
装饰器以对filter_comm
和comm
进行应做的任何工作,然后根据这些结果决定是否执行它包装的功能,并传入nworkitems
。
它不像newcomm
那样优雅,但是我认为它比其他建议更易读,更接近您想要的内容。如果您不喜欢内部函数,可以用with
之外的其他名字命名,但是我选择了内部函数,因为它是当语法要求一个您永远不会真正使用的名称时,Python中使用的普通名称。