Python上下文管理器:有条件的执行主体?

问题描述 投票:15回答:3

我正在编写一个基于MPI的应用程序(但是MPI在我的问题中并不重要,我只提到它只是为了揭示其原理),在某些情况下,当工作项少于流程时,我需要创建一个新的沟通者,不包括与工作无关的流程。最后,新的沟通者必须由有工作要做的流程释放出来(并且只有它们自己才能完成)。

一种简洁的方法是写:

with filter_comm(comm, nworkitems) as newcomm:
    ... do work with communicator newcomm...

主体仅由有工作要做的过程执行。

上下文管理器中是否有一种方法可以避免执行主体?我知道上下文管理器的设计应避免隐藏控制流,但是我想知道是否有可能规避这一点,因为在我看来,为清晰起见,这是合理的。

python contextmanager
3个回答
11
投票

已经提出了有条件跳过上下文管理器主体的功能,但是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)

6
投票

此功能似乎是workwithdriver(devicename, dowork) 。 Python开发人员通常更喜欢显式变体:

rejected

您也可以使用高阶函数:

if need_more_workers():
    newcomm = get_new_comm(comm)
    # ...

1
投票

类似这样的事情如何:

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_commcomm进行应做的任何工作,然后根据这些结果决定是否执行它包装的功能,并传入nworkitems

它不像newcomm那样优雅,但是我认为它比其他建议更易读,更接近您想要的内容。如果您不喜欢内部函数,可以用with之外的其他名字命名,但是我选择了内部函数,因为它是当语法要求一个您永远不会真正使用的名称时,Python中使用的普通名称。

© www.soinside.com 2019 - 2024. All rights reserved.