Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

re-raising an exception in a context handler

From the datamodel docs on context managers:

Note that __exit__() methods should not reraise the passed-in exception; this is the caller’s responsibility.


I have a temporary file, whose file descriptor I'd like to free with close but without writing anything to disk. My intuitive solution was to pass on the exception, but that is discouraged in the docs - and surely for good reasons.

class Processor(object):
    ...
    def write(self, *args, **kwargs):
        if something_bad_happens:
            raise RuntimeError('This format expects %s columns: %s, got %s.' % (
                               (len(self.cols), self.cols, len(args))))
        self.writer.writerow(args)

    def __enter__(self):
        return self

    def __exit__(self, type, value, traceback):
        # the RuntimeError from write will be set as type, value and so on ..
        # I'd like to close the stream here (release the file descriptor), 
        # but I do not leave a trace of the associated file - 
        # (one can always 'manually' delete with `os.remove` but maybe there's a 
        # better way ..?)
        self.output_pipe.close()

Also I do not want in this particular case error handling in the caller for two reasons:

  • to keep the code in the caller minimal (see below)
  • the caller is happy with exceptions (fail fast is what we want here)

The context manager is used something like this:

class Worker(object):
    ...
    def run(self):
        # output setup so it will emit a three column CSV
        with self.output().open('w') as output:
            output.write('John', 'CA', 92101)
            output.write('Jane', 'NY', 10304)
            # should yield an error, since only three 'columns' are allowed 
            output.write('Hello', 'world')

update: My quesion was a bit ill-formulated as my problem really boiled down to this: In nested context managers, how can I pass an exception on to the outermost CM?

like image 365
miku Avatar asked Aug 23 '13 08:08

miku


People also ask

How can an exception be re raised?

If an exception occurs during execution of the try clause, the exception may be handled by an except clause. If the exception is not handled by an except clause, the exception is re-raised after the finally clause has been executed.

How do you raise an exception from another exception?

Let's consider a situation where we want to raise an exception in response to catching a different exception but want to include information about both exceptions in the traceback. To chain exceptions, use the raise from statement instead of a simple raise statement. This will give you information about both errors.

What is a context manager Python?

A context manager usually takes care of setting up some resource, e.g. opening a connection, and automatically handles the clean up when we are done with it. Probably, the most common use case is opening a file. with open('/path/to/file.txt', 'r') as f: for line in f: print(line)


1 Answers

  • When __exit__ returns True, any exception passed to it is swallowed.
  • When __exit__ returns False, the exception is reraised.

def __exit__(self, type, value, traceback):
    self.output_pipe.close()  # always close the file
    if type is not None: # an exception has occurred
        os.unlink(...)   # remove the file
        return False     # reraise the exception

You could of course omit the return False since Python will return None (which is Falsish) by default.


By the way, is self.output() an instance of Processor? If so,

with self.output().open('w') as output:

should be

with self.output() as output:

In any case, it would be nicer if you could arrange for the latter to be the correct syntax. You might have to change __enter__ to:

def __enter__(self):
    return self.output_pipe.open('w')
like image 175
unutbu Avatar answered Nov 15 '22 23:11

unutbu