How does contextmanager forward a raised exception to the function that it decorates?

Question:

How do I write a class that can be used in place of contextmanager in this example?

from contextlib import contextmanager

@contextmanager
def f():
    try: 
        yield None
    except Exception as e:
        print(e) # This statement is executed!

with f() as _:
    raise Exception("foo")

My attempt:

class MyOwnContextManager:
    def __init__(self, f):
        self.f = f

    def __enter__(self):
        return self.g.__next__()        

    def __exit__(self, type, value, traceback):
        pass # What exactly do I put here?

    def __call__(self, *args, **kwds):
        self.g = self.f(*args, **kwds)
        return self

@MyOwnContextManager
def f():
    try: 
        yield None
    except Exception as e:
        print(e) # This statement is NOT executed!

with f() as _:
    raise Exception("foo")

MyOwnContextManager doesn’t let f handle the raised Exception, unlike the built-in contextmanager, which calls print(e). How do I handle the exception that is raised inside of the context with the function that is decorated by MyOwnContextManager?

Asked By: Tsiolkovsky

||

Answers:

The magic happens with self.g.throw where you pass it back to your own block.


class MyOwnContextManager:
    def __init__(self, f):
        self.f = f

    def __enter__(self):
        return self.g.__next__()        

    def __exit__(self, type, value, traceback):
        try:
            self.g.throw(type, value, traceback) # go back to your function
            raise RuntimeError("generator didn't stop after throw()")
        except StopIteration as exc:
            print("stopping itter returning", exc is not value, value)
            return exc is not value

    def __call__(self, *args, **kwds):
        self.g = self.f(*args, **kwds)
        return self

@MyOwnContextManager
def f():
    print("init")
    try: 
        print("yielding")
        yield None
        print("yiedled") # will not happen
    except Exception as e:
        print("handler", e) 

Output

init  
yielding  
go  
handler foo
stopping itter returning True <class 'Exception'> foo
Answered By: Daraan
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.