Пропуск выполнения -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(), чтобы выполнять свою работу, поэтому использование его самостоятельно мешает этому. Есть много причин, почему вы не должны использовать этот код. Просто к вашему сведению.