Ответ 1
Было предложено и отклонено возможность условного пропуска тела менеджера контекста, как описано в PEP 377.
Ниже приведены некоторые способы достижения функциональности.
Во-первых, что не работает: не получается из генератора contextmanager.
@contextlib.contextmanager
def drivercontext():
driver, ok = driverfactory()
try:
if ok:
yield driver
else:
print 'skip because driver not ok'
finally:
driver.quit()
with drivercontext() as driver:
dostuff(driver)
Невыполнение приведет к a RuntimeException
, поднятому на contextmanager
. По крайней мере, finally
надежно выполняется.
Способ 1. Пропустите тело вручную.
@contextlib.contextmanager
def drivercontext():
driver, ok = driverfactory()
try:
yield driver, ok
finally:
driver.quit()
with drivercontext() as (driver, ok):
if ok:
dostuff(driver)
else:
print 'skip because driver not ok'
Это, хотя и явное, отрицает большую часть краткости тела менеджера контекста. Логика, которая должна быть скрыта в менеджере контекста, выливается в тело и должна повторяться для каждого вызова.
Способ 2: используйте генератор.
def drivergenerator():
driver, ok = driverfactory()
try:
if ok:
yield driver
else:
print 'skip because driver not ok'
finally:
driver.quit()
for driver in drivergenerator():
dostuff(driver)
Это ведет себя очень как менеджер контекста, который может пропустить тело. К сожалению, это очень похоже на цикл.
Способ 3: сделать все вручную.
driver, ok = driverfactory()
try:
if ok:
dostuff(driver)
else:
print 'skip because driver not ok'
finally:
driver.quit()
Ба. Что это? Многословность соперничает с Java.
Обобщение этого может быть сделано только с обратным вызовом.
def withdriver(callback):
driver, ok = driverfactory()
try:
if ok:
callback(driver)
else:
print 'skip because driver not ok'
finally:
driver.quit()
withdriver(dostuff)
Хорошо. Диспетчер контекста решает много случаев. Но всегда есть трещины. Это напоминает мне закон негерметичных абстракций.
Вот несколько примеров, демонстрирующих эти методы и некоторые другие методы.
import contextlib
import functools
# ----------------------------------------------------------------------
# this code is a simulation of the code not under my control
# report and ok and fail are variables for use in the simulation
# they do not exist in the real code
# report is used to report certain checkpoints
# ok is used to tell the driver object whether it is ok or not
# fail is used tell dostuff whether it should fail or not
class Driver(object):
def __init__(self, report, ok):
# driver can be ok or not ok
# driver must always quit after use
# regardless if it is ok or not
print 'driver init (ok: %s)' % ok
self.report = report
def drivestuff(self):
# if driver is not ok it is not ok to do stuff with it
self.report.drivestuffrun = True
def quit(self):
# driver must always quit regardless of ok or not
print 'driver quit'
self.report.driverquit = True
def driverfactory(report, ok=True):
# driver factory always returns a driver
# but sometimes driver is not ok
# this is indicated by second return value
# not ok driver must still be quit
return Driver(report, ok), ok
class DoStuffFail(Exception):
pass
def dostuff(driver, fail=False):
# this method does a lot of stuff
# dostuff expects an ok driver
# it does not check whether the driver is ok
driver.drivestuff()
# do stuff can also fail independent of driver
if fail:
print 'dostuff fail'
raise DoStuffFail('doing stuff fail')
else:
print 'dostuff'
# ----------------------------------------------------------------------
class AbstractScenario(object):
def __init__(self, driverfactory, dostuff):
self.driverfactory = functools.partial(driverfactory, report=self)
self.dostuff = dostuff
self.driverquit = False
self.drivestuffrun = False
# ----------------------------------------------------------------------
class Scenario0(AbstractScenario):
def run(self):
print '>>>> not check driver ok and not ensure driver quit'
driver, ok = self.driverfactory()
self.dostuff(driver)
driver.quit()
# ----------------------------------------------------------------------
class Scenario1(AbstractScenario):
def run(self):
print '>>>> check driver ok but not ensure driver quit'
driver, ok = self.driverfactory()
if ok:
self.dostuff(driver)
else:
print 'skip because driver not ok'
driver.quit()
# ----------------------------------------------------------------------
class Scenario2(AbstractScenario):
def run(self):
print '>>>> check driver ok and ensure driver quit'
driver, ok = self.driverfactory()
try:
if ok:
self.dostuff(driver)
else:
print 'skip because driver not ok'
finally:
driver.quit()
# ----------------------------------------------------------------------
class Scenario3(AbstractScenario):
@contextlib.contextmanager
def drivercontext(self, driverfactory):
driver, ok = driverfactory()
try:
if ok:
yield driver
else:
print 'skip because driver not ok'
finally:
driver.quit()
def run(self):
print '>>>> skip body by not yielding (does not work)'
with self.drivercontext(self.driverfactory) as driver:
self.dostuff(driver)
# ----------------------------------------------------------------------
class Scenario4(AbstractScenario):
@contextlib.contextmanager
def drivercontext(self, driverfactory):
driver, ok = driverfactory()
try:
yield driver, ok
finally:
driver.quit()
def run(self):
print '>>>> skip body manually by returning flag with context'
with self.drivercontext(self.driverfactory) as (driver, ok):
if ok:
self.dostuff(driver)
else:
print 'skip because driver not ok'
# ----------------------------------------------------------------------
class Scenario5(AbstractScenario):
def drivergenerator(self, driverfactory):
driver, ok = driverfactory()
try:
if ok:
yield driver
else:
print 'skip because driver not ok'
finally:
driver.quit()
def run(self):
print '>>>> abuse generator as context manager'
for driver in self.drivergenerator(self.driverfactory):
self.dostuff(driver)
# ----------------------------------------------------------------------
def doscenarios(driverfactory, dostuff, drivestuffrunexpected=True):
for Scenario in AbstractScenario.__subclasses__():
print '-----------------------------------'
scenario = Scenario(driverfactory, dostuff)
try:
try:
scenario.run()
except DoStuffFail as e:
print 'dostuff fail is ok'
if not scenario.driverquit:
print '---- fail: driver did not quit'
if not scenario.drivestuffrun and drivestuffrunexpected:
print '---- fail: drivestuff did not run'
if scenario.drivestuffrun and not drivestuffrunexpected:
print '---- fail: drivestuff did run'
except Exception as e:
print '----- fail with exception'
print '--------', e
# ----------------------------------------------------------------------
notokdriverfactory = functools.partial(driverfactory, ok=False)
dostufffail = functools.partial(dostuff, fail=True)
print '============================================'
print '==== driver ok and do stuff will not fail =='
doscenarios(driverfactory, dostuff)
print '============================================'
print '==== do stuff will fail ================='
doscenarios(driverfactory, dostufffail)
print '==========================================='
print '===== driver is not ok ==================='
doscenarios(notokdriverfactory, dostuff, drivestuffrunexpected=False)
И вывод.
============================================
==== driver ok and do stuff will not fail ==
-----------------------------------
>>>> not check driver ok and not ensure driver quit
driver init (ok: True)
dostuff
driver quit
-----------------------------------
>>>> check driver ok but not ensure driver quit
driver init (ok: True)
dostuff
driver quit
-----------------------------------
>>>> check driver ok and ensure driver quit
driver init (ok: True)
dostuff
driver quit
-----------------------------------
>>>> skip body by not yielding (does not work)
driver init (ok: True)
dostuff
driver quit
-----------------------------------
>>>> skip body manually by returning flag with context
driver init (ok: True)
dostuff
driver quit
-----------------------------------
>>>> abuse generator as context manager
driver init (ok: True)
dostuff
driver quit
============================================
==== do stuff will fail =================
-----------------------------------
>>>> not check driver ok and not ensure driver quit
driver init (ok: True)
dostuff fail
dostuff fail is ok
---- fail: driver did not quit
-----------------------------------
>>>> check driver ok but not ensure driver quit
driver init (ok: True)
dostuff fail
dostuff fail is ok
---- fail: driver did not quit
-----------------------------------
>>>> check driver ok and ensure driver quit
driver init (ok: True)
dostuff fail
driver quit
dostuff fail is ok
-----------------------------------
>>>> skip body by not yielding (does not work)
driver init (ok: True)
dostuff fail
driver quit
dostuff fail is ok
-----------------------------------
>>>> skip body manually by returning flag with context
driver init (ok: True)
dostuff fail
driver quit
dostuff fail is ok
-----------------------------------
>>>> abuse generator as context manager
driver init (ok: True)
dostuff fail
driver quit
dostuff fail is ok
===========================================
===== driver is not ok ===================
-----------------------------------
>>>> not check driver ok and not ensure driver quit
driver init (ok: False)
dostuff
driver quit
---- fail: drivestuff did run
-----------------------------------
>>>> check driver ok but not ensure driver quit
driver init (ok: False)
skip because driver not ok
driver quit
-----------------------------------
>>>> check driver ok and ensure driver quit
driver init (ok: False)
skip because driver not ok
driver quit
-----------------------------------
>>>> skip body by not yielding (does not work)
driver init (ok: False)
skip because driver not ok
driver quit
----- fail with exception
-------- generator didn't yield
-----------------------------------
>>>> skip body manually by returning flag with context
driver init (ok: False)
skip because driver not ok
driver quit
-----------------------------------
>>>> abuse generator as context manager
driver init (ok: False)
skip because driver not ok
driver quit