Question

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?

Was it helpful?

Solution

  • 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')

OTHER TIPS

There is no need to raise the exception; the exception has already been raised and your context manager is merely being informed of that.

Test if there was no exception instead:

if type is None:
    # if no exception is raised, proceed as usual:
    self.output_pipe.close()

If your context manager were to return True at that moment you'd suppress the exception; just exiting the function instead returns None and the exception remains 'raised'.

Note that the tempfile module includes two types of temporary file objects that act as context managers, that self-delete already, independent of platform. On POSIX systems you can unlink the file right after creation; the file descriptor remains alive until you close the file. Windows offers a 'delete on close' option too. The tempfile.NamedTemporaryFile() class uses the right option for your platform.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top