跳过-with-block的执行

问题描述 投票:16回答:4

我正在定义一个上下文管理器类,如果在实例化期间满足某些条件,我希望能够跳过代码块而不引发异常。例如,

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...'
python with-statement skip
4个回答
13
投票

如果你想要一个使用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 ... '

14
投票

根据PEP-343with声明翻译自:

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”)。

人们已经完成了特定于Python实现的事情,比如在__enter__()等项目中操纵withhacks中的调用堆栈。我记得亚历克斯·马尔泰利(Alex Martelli)在一年或两年后发布了一篇关于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语句。


1
投票

来自withhacks(特别是来自AnonymousBlocksInPython)的其他答案中提到的对hack的python 3更新:

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')

正如乔前面提到的,这是一个应该避免的黑客攻击:

输入新的本地作用域时调用方法trace(),即当with块中的代码开始时。当引发异常时,它会被exit()捕获。这就是黑客的工作方式。我应该补充说,这非常黑客,不应该依赖。神奇的sys.settrace()实际上并不是语言定义的一部分,它恰好出现在CPython中。此外,调试器依靠sys.settrace()来完成它们的工作,因此自己使用它会干扰它。您不应该使用此代码的原因有很多。仅供参考。

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