Пропуск выполнения -with-block
Я определяю класс менеджера контекста, и я хотел бы иметь возможность пропускать блок кода без привлечения исключения, если во время создания экземпляра выполняются определенные условия. Например,
class My_Context(object):
def __init__(self,mode=0):
"""
if mode = 0, proceed as normal
if mode = 1, do not execute block
"""
self.mode=mode
def __enter__(self):
if self.mode==1:
print 'Exiting...'
CODE TO EXIT PREMATURELY
def __exit__(self, type, value, traceback):
print 'Exiting...'
with My_Context(mode=1):
print 'Executing block of codes...'
Ответы
Ответ 1
Если вам нужно специальное решение, которое использует идеи из withhacks (в частности, из AnonymousBlocksInPython), это будет работать:
import sys
import inspect
class My_Context(object):
def __init__(self,mode=0):
"""
if mode = 0, proceed as normal
if mode = 1, do not execute block
"""
self.mode=mode
def __enter__(self):
if self.mode==1:
print 'Met block-skipping criterion ...'
# Do some magic
sys.settrace(lambda *args, **keys: None)
frame = inspect.currentframe(1)
frame.f_trace = self.trace
def trace(self, frame, event, arg):
raise
def __exit__(self, type, value, traceback):
print 'Exiting context ...'
return True
Сравните следующее:
with My_Context(mode=1):
print 'Executing block of code ...'
с
with My_Context(mode=0):
print 'Executing block of code ... '
Ответ 2
Согласно PEP-343, оператор with
преобразуется из:
with EXPR as VAR:
BLOCK
в
mgr = (EXPR)
exit = type(mgr).__exit__ # Not calling it yet
value = type(mgr).__enter__(mgr)
exc = True
try:
try:
VAR = value # Only if "as VAR" is present
BLOCK
except:
# The exceptional case is handled here
exc = False
if not exit(mgr, *sys.exc_info()):
raise
# The exception is swallowed if exit() returns true
finally:
# The normal and non-local-goto cases are handled here
if exc:
exit(mgr, None, None, None)
Как вы можете видеть, нет ничего очевидного, что вы можете сделать из вызова метода __enter__()
диспетчера контекста, который может пропустить тело ( "BLOCK
" ) оператора with.
Люди выполняли специфические для Python вещи, такие как манипулирование стеком вызовов внутри __enter__()
, в таких проектах, как withhacks. Я вспоминаю, как Алекс Мартелли отправляет очень интересный хакер на stackoverflow через год или два назад (не помните, чтобы почта была найдена для поиска и поиска).
Но простой ответ на ваш вопрос/проблему заключается в том, что вы не можете делать то, что вы просите, пропуская тело оператора with, не прибегая к так называемой "глубокой магии" (которая не обязательно переносима между реализациями python). С глубокой магией вы могли бы это сделать, но я рекомендую делать такие вещи, как упражнение, как это можно сделать, а не в "производственном коде".
Ответ 3
То, что вы пытаетесь сделать, невозможно, к сожалению. Если __enter__
вызывает исключение, это исключение выражается в инструкции with
(__exit__
не вызывается). Если он не вызывает исключение, тогда возвращаемое значение подается на блок и выполняется блок.
Ближайшая вещь, о которой я мог думать, - это флаг, явно отмеченный блоком:
class Break(Exception):
pass
class MyContext(object):
def __init__(self,mode=0):
"""
if mode = 0, proceed as normal
if mode = 1, do not execute block
"""
self.mode=mode
def __enter__(self):
if self.mode==1:
print 'Exiting...'
return self.mode
def __exit__(self, type, value, traceback):
if type is None:
print 'Normal exit...'
return # no exception
if issubclass(type, Break):
return True # suppress exception
print 'Exception exit...'
with MyContext(mode=1) as skip:
if skip: raise Break()
print 'Executing block of codes...'
Это также позволяет повысить Break()
в середине блока with
, чтобы имитировать нормальный оператор break
.
Ответ 4
Обновление Python 3 к хаку, упомянутому в других ответах от withhacks (в частности, от AnonymousBlocksInPython):
class SkipWithBlock(Exception):
pass
class SkipContextManager:
def __init__(self, skip):
self.skip = skip
def __enter__(self):
if self.skip:
sys.settrace(lambda *args, **keys: None)
frame = sys._getframe(1)
frame.f_trace = self.trace
def trace(self, frame, event, arg):
raise SkipWithBlock()
def __exit__(self, type, value, traceback):
if type is None:
return # No exception
if issubclass(type, SkipWithBlock):
return True # Suppress special SkipWithBlock exception
with SkipContextManager(skip=True):
print('In the with block') # Won't be called
print('Out of the with block')
Как упоминалось ранее Джо, это хак, которого следует избегать:
Я должен добавить, что это очень большой взлом и на него нельзя полагаться. Волшебный sys.settrace() на самом деле не является частью определения языка, он просто происходит в CPython. Кроме того, отладчики полагаются на sys.settrace(), чтобы выполнять свою работу, поэтому использование его самостоятельно мешает этому. Есть много причин, почему вы не должны использовать этот код. Просто к вашему сведению.