2016-02-16 2 views
1

Я хочу, чтобы заменить __enter__/__exit__ функции класса с одной функцией, украшенной, как contextlib.contextmanager, вот код:Почему исключение для ретранслятора контенту не требуется?

class Test: 
    def __enter__(self): 
     self._cm_obj = self._cm() 
     self._cm_obj.__enter__() 

    def __exit__(self, exc_type, exc_val, exc_tb): 
     try: 
      # Here we first time caught exception, 
      # Pass it to _cm: 
      self._cm_obj.__exit__(exc_type, exc_val, exc_tb) 
     except: 
      # Here we should catch exception reraised by _cm, 
      # but it doesn't happen. 
      raise 
     else: 
      return True 

    @contextmanager 
    def _cm(self): 
     print('enter') 
     try: 
      yield 
     except: 
      # Here we got exception from __exit__ 
      # Reraise it to tell __exit__ it should be raised. 
      raise 
     finally: 
      print('exit') 


with Test(): 
    raise Exception(123) 

Когда мы получили исключение в Test-х __exit__, мы передаем его в _cm «ы __exit__, он отлично работает, я см. исключение внутри _cm. Но тогда, когда я решаю сделать ререйз внутри _cm, этого не происходит: после теста __exit__ я не вижу исключения (и код работает неправильно, без исключения).

Почему не exeprion ререйз внутри __exit__?

Если это нормальное поведение, не могли бы вы посоветовать мне решение правильно заменить __enter__/__exit__ с функцией contextmanager?

Редактировать:

cm_obj.__exit__ возвращает None на исключение внутри _cm и True, если исключение было подавлено (или не поднятая вообще).

С другой стороны, внутри Test.__exit__ мы можем вернуть None для распространения текущего исключения или True для его подавления.

Похоже, просто возвращается значение cm_obj.__exit__ «s внутри Test.__exit__ сделать работу, этот код работает, как я хочу:

class Test: 
    def __enter__(self): 
     self._cm_obj = self._cm() 
     self._cm_obj.__enter__() 

    def __exit__(self, exc_type, exc_val, exc_tb): 
     return self._cm_obj.__exit__(exc_type, exc_val, exc_tb) 

    @contextmanager 
    def _cm(self): 
     print('---- enter') 
     try: 
      yield 
     except: 
      raise # comment to suppess exception 
      pass 
     finally: 
      print('---- exit') 


with Test(): 
    raise Exception(123) 

ответ

2

Исключение не поднятый в __exit__, так что нет ничего, чтобы повторно рейза ,

Исключение передается методу в качестве аргументов, поскольку вы не можете возбуждать исключение в другом методе (кроме генератора). Вам также не нужно поднимать исключение, вам нужно просто вернуть , а не вернуть истинное значение для распространяемого исключения. Достаточно вернуть None.

От With Statement Context Managers documentation:

Выйти из контекста выполнения связанных с этим объектом. Параметры описывают исключение, вызвавшее выход из контекста. Если контекст был исключен без исключения, все три аргумента будут None.

Если предоставляется исключение, и метод хочет исключить исключение (т. Е. Предотвратить его распространение), он должен вернуть истинное значение. В противном случае исключение будет обработано обычно после выхода из этого метода.

Отметьте, что методы __exit__() не должны реместировать исключение в качестве исключения; это обязанность звонящего.

Смелый акцент мой.

Вы вместо того, чтобы подавить результат self._cm_obj.__exit__() вызова и возврата True обеспечить исключение не воскреснет:

def __exit__(self, exc_type, exc_val, exc_tb): 
    self._cm_obj.__exit__(exc_type, exc_val, exc_tb) 
    return True 

Если вы не вернулись True здесь вы увидите исключение ПЕРЕУСТАНОВКИ поднятый оператором with.

Вы не можете повторно поднимать исключения, которые не попадают в контекстный менеджер. Если вы поймаете и обработаете исключение в блоке кода (например, внутри _cm), менеджер контекста никогда не будет проинформирован об этом. Подавленные исключения старт с подавлением.

Отметьте, что @contextmanager не может изменить эти правила; хотя он передает исключение в генератор с generator.throw(), он также должен поймать то же самое исключение, если ваш генератор не справится с этим. Вместо этого он вернет ложное значение в этот момент, потому что это то, что должен сделать contextmanager.__exit__() метод.

+0

«возвращает истину, чтобы обеспечить исключение не воскреснет» - в этом случае каждое исключение будет подавлен, но я хочу его быть подавлен только в том случае, если он был подавлен внутри _cm() и ререйзирован, если он не был подавлен внутри _cm() –

+0

@germn: вы не можете. Подавленные исключения в блоке не доходят до контекстного менеджера. –

+1

«вы не можете возбудить исключение в другом методе (кроме генератора)» - это одна из причин, по которой '@ contextmanager' хочет, чтобы вы дали ему генератор. – user2357112

1

Когда метод менеджера контекста должен указывать, что исключение должно распространяться, оно не делает этого, распространяя исключение из __exit__. Он делает это, возвращая значение фальши.

@contextmanager предоставляет вам другой API, но ему все равно нужно преобразовать его в обычный API контекстного менеджера, чтобы показать Python. Когда исключение, переданное @contextmanager, менеджер контекста __exit__ распространяется из завернутой функции uncaught, @contextmanager ловит исключение и возвращает значение фальшивки от __exit__. Это означает, что этот призыв:

self._cm_obj.__exit__(exc_type, exc_val, exc_tb) 

не вызывает исключение, потому что она поймана @contextmanager.

Вы можете увидеть код, где @contextmanager ловит исключение в Lib/contextlib.py

  try: 
       self.gen.throw(type, value, traceback) 
       raise RuntimeError("generator didn't stop after throw()") 
      except StopIteration, exc: 
       # Suppress the exception *unless* it's the same exception that 
       # was passed to throw(). This prevents a StopIteration 
       # raised inside the "with" statement from being suppressed 
       return exc is not value 
      except: 
       # only re-raise if it's *not* the exception that was 
       # passed to throw(), because __exit__() must not raise 
       # an exception unless __exit__() itself failed. But throw() 
       # has to raise the exception to signal propagation, so this 
       # fixes the impedance mismatch between the throw() protocol 
       # and the __exit__() protocol. 
       # 
       if sys.exc_info()[1] is not value: 
        raise 
Смежные вопросы