bataliero1234
bataliero1234

Reputation: 165

Why __exit__ is not executed when __enter__ raises exception

I was wondering lately what was the reason behind not calling implicitly __exit__ when __enter__ raises exception? Why it was designed in such way? I was implementing service runner class to be usable by 'with' keyword and it turned out that __exit__ is never called.

Example:

class ServiceRunner(object):
    def __init__(self, allocation_success):
        self.allocation_success = allocation_success

    def _allocate_resource(self):
        print("Service1 running...")
        print("Service2 running...")

        # running service3 fails ...
        if not self.allocation_success:
            raise RuntimeError("Service3 failed!")
        print("Service3 running...")

    def _free_resource(self):
        print("All services freed.")

    def __enter__(self):
        self._allocate_resource()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self._free_resource()

Usage:

with ServiceRunner(allocation_success=True):
    pass

try:
    with ServiceRunner(allocation_success=False):
        pass
    except Exception as e:
        print(e)

Output:

Service1 running...
Service2 running...    
Service3 running...
All services freed.

and

Service1 running...
Service2 running...
Service3 failed!

Function __exit__ is not called. Service1 and Service2 are not freed.

I could move _allocate_resource() to __init__ but then class is not very useful in such usage:

try:
    runner = ServiceRunner(allocation_success=True)
except Exception as e:
    print(e)
else:
    with runner as r:
        r.do()

    with runner as r:
        r.do()

Output:

Service1 running...
Service2 running...
Service3 running...
All services freed.
All services freed.

Services are not started again.

I could reimplement __enter__ to handle exceptions but it adds some boilerplate code to the function:

def __enter__(self):
    try:
        self._allocate_resource()
    except Exception as e:
        self.__exit__(*sys.exc_info())
        raise e

Is it the best solution?

Upvotes: 5

Views: 1831

Answers (1)

leongold
leongold

Reputation: 1044

If you've failed to enter a context, there's no reason to attempt to exit it, i.e. if you've failed to allocate a resource, there's no reason to attempt to release it.

IIUC, what you're looking for is simply:

try:
   with ServiceRunner() as runner:
       runner.do()
except Exception as e:
    print(e)

Upvotes: 3

Related Questions