已提出有条件地跳过上下文管理器主体的能力,但如PEP 377中所述被拒绝。
我做了一些关于替代品的研究。这是我的发现。
首先让我解释一下我的代码示例的背景。您有一堆想要使用的设备。对于每个设备,您必须获取设备的驱动程序;然后使用驱动程序使用设备;最后释放驱动程序,以便其他人可以获取驱动程序并使用该设备。
这里没有什么不寻常的。代码大致如下:
driver = getdriver(devicename)
try:
dowork(driver)
finally:
releasedriver(driver)
但是,当行星没有正确对齐的每个满月时,获得的设备驱动程序是错误的,并且无法对设备进行任何操作。这没什么大不了的。只需在这一轮跳过该设备,然后在下一轮重试。通常司机是好的。但即使是坏的驱动程序也需要发布,否则无法获得新的驱动程序。
(固件是专有的,供应商不愿意修复甚至承认这个错误)
代码现在如下所示:
driver = getdriver(devicename)
try:
if isgooddriver(driver):
dowork(driver)
else:
handledrivererror(geterrordetails(driver))
finally:
release(driver)
每次需要使用设备完成工作时,都需要重复大量样板代码。python上下文管理器的主要候选者,也称为with 语句。它可能看起来像这样:
# note: this code example does not work
@contextlib.contextmanager
def contextgetdriver(devicename):
driver = getdriver(devicename)
try:
if isgooddriver(driver):
yield driver
else:
handledrivererror(geterrordetails(driver))
finally:
release(driver)
然后使用设备时的代码又短又甜:
# note: this code example does not work
with contextgetdriver(devicename) as driver:
dowork(driver)
但这不起作用。因为上下文管理器必须让步。它可能不会产生。不屈服将导致RuntimeException
提高 by contextmanager
。
所以我们必须从上下文管理器中取出检查
@contextlib.contextmanager
def contextgetdriver(devicename):
driver = getdriver(devicename)
try:
yield driver
finally:
release(driver)
并将其放在with
语句的正文中
with contextgetdriver(devicename) as driver:
if isgooddriver(driver):
dowork(driver)
else:
handledrivererror(geterrordetails(driver))
这很丑陋,因为现在我们再次有一些样板文件,每次我们想要使用设备时都需要重复这些样板文件。
所以我们想要一个可以有条件地执行主体的上下文管理器。但我们没有,因为PEP 377(正是建议这个特性)被拒绝了。
我们可以自己引发异常,而不是不让步:
@contextlib.contextmanager
def contextexceptgetdriver(devicename):
driver = getdriver(devicename)
try:
if isgooddriver(driver):
yield driver
else:
raise NoGoodDriverException(geterrordetails(driver))
finally:
release(driver)
但现在您需要处理异常:
try:
with contextexceptgetdriver(devicename) as driver:
dowork(driver)
except NoGoodDriverException as e:
handledrivererror(e.errordetails)
它的代码复杂性成本与上面对好的驱动程序的显式测试几乎相同。
不同之处:有一个例外,我们可以决定不在这里处理它,而是让它冒泡调用堆栈并在其他地方处理它。
还有区别:当我们处理异常时,驱动程序已经被释放了。虽然通过显式检查,驱动程序尚未发布。(except 在 with 语句之外,而 else 在 with 语句内)
我发现滥用生成器可以很好地替代可以跳过正文的上下文管理器
def generatorgetdriver(devicename):
driver = getdriver(devicename)
try:
if isgooddriver(driver):
yield driver
else:
handledrivererror(geterrordetails(driver))
finally:
release(driver)
但是调用代码看起来很像一个循环
for driver in generatorgetdriver(devicename):
dowork(driver)
如果您可以忍受这个(请不要),那么您就有一个可以有条件地执行正文的上下文管理器。
似乎防止样板代码的唯一方法是使用回调
def workwithdevice(devicename, callback):
driver = getdriver(devicename)
try:
if isgooddriver(driver):
callback(driver)
else:
handledrivererror(geterrordetails(driver))
finally:
release(driver)
和调用代码
workwithdevice(devicename, dowork)