我正在定义一个上下文管理器类,如果在实例化期间满足某些条件,我希望能够跳过代码块而不会引发异常。例如,
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...'
答案 0 :(得分:12)
根据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__()
方法,可以跳过with语句的正文(“BLOCK
”),没有什么可以做的。 / p>
人们已经完成了特定于Python实现的事情,比如在withhacks等项目中操纵__enter__()
内的调用堆栈。我记得亚历克斯·马尔泰利(Alex Martelli)在一年或两年后发布了一篇关于stackoverflow非常有趣的黑客攻击(不要回想起足够的帖子来搜索并找到它)。
但是对你的问题/问题的简单回答是,你不能做你所要求的,跳过with语句的主体,而不诉诸所谓的“深层魔术”(在python实现之间不一定是可移植的) )。有了深刻的魔力,你或许可以做到,但我建议只做一些练习,看看它是如何完成的,而不是在“生产代码”中。
答案 1 :(得分:12)
如果您想要一个使用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 :(得分: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
语句。
答案 3 :(得分:2)
基于@Peter的答案,这是一个不使用任何字符串操作,但其他方式应该相同的版本:
from contextlib import contextmanager
@contextmanager
def skippable_context(skip):
skip_error = ValueError("Skipping Context Exception")
prev_entered = getattr(skippable_context, "entered", False)
skippable_context.entered = False
def command():
skippable_context.entered = True
if skip:
raise skip_error
try:
yield command
except ValueError as err:
if err != skip_error:
raise
finally:
assert skippable_context.entered, "Need to call returned command at least once."
skippable_context.entered = prev_entered
print("=== Running with skip disabled ===")
with skippable_context(skip=False) as command:
command()
print("Entering this block")
print("... Done")
print("=== Running with skip enabled ===")
with skippable_context(skip=True) as command:
command()
raise NotImplementedError("... But this will never be printed")
print("... Done")
答案 4 :(得分:2)
上下文管理器不是正确的构造。您要求正文执行 n 次,在这种情况下为零或一。如果查看一般情况 n ,其中 n > = 0,则最终会出现for循环:
def do_squares(n):
for i in range(n):
yield i ** 2
for x in do_squares(3):
print('square: ', x)
for x in do_squares(0):
print('this does not print')
在您的情况下,这是更特殊的用途,不需要绑定到循环变量:
def should_execute(mode=0):
if mode == 0:
yield
for _ in should_execute(0):
print('this prints')
for _ in should_execute(1):
print('this does not')
答案 5 :(得分:1)
针对hack的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')
如joe之前所提到的,应该避免这种黑客行为:
当输入新的本地范围时,即在with块中的代码开始时,将调用trace()方法。在此处引发异常时,它会被 exit ()捕获。这就是这种黑客的工作方式。我还应该补充一点,这是一个很大的漏洞,不应依赖。神奇的sys.settrace()实际上不是语言定义的一部分,它恰好在CPython中。另外,调试器依靠sys.settrace()来完成其工作,因此您自己使用它会对此产生干扰。有许多原因使您不应该使用此代码。仅供参考。
答案 6 :(得分:0)
另一个稍微有点怪异的选项使用了exec
。这很方便,因为可以对其进行修改以执行任意操作(例如,上下文块的记忆):
from contextlib import contextmanager
@contextmanager
def skippable_context_exec(skip):
SKIP_STRING = 'Skipping Context Exception'
old_value = skippable_context_exec.is_execed if hasattr(skippable_context_exec, 'is_execed') else False
skippable_context_exec.is_execed=False
command = "skippable_context_exec.is_execed=True; "+("raise ValueError('{}')".format(SKIP_STRING) if skip else '')
try:
yield command
except ValueError as err:
if SKIP_STRING not in str(err):
raise
finally:
assert skippable_context_exec.is_execed, "You never called exec in your context block."
skippable_context_exec.is_execed = old_value
print('=== Running with skip disabled ===')
with skippable_context_exec(skip=False) as command:
exec(command)
print('Entering this block')
print('... Done')
print('=== Running with skip enabled ===')
with skippable_context_exec(skip=True) as command:
exec(command)
print('... But this will never be printed')
print('... Done')
最好有一些可以摆脱执行程序而又没有怪异副作用的东西,所以如果您能想到一种方法,我会不知所措。这个问题的current lead answer似乎可以解决,但是有some issues。