Should a function that returns a context-managed object be decorated by `@contextmanager`?

Question:

Say I have a function that returns a context-managed object, here a tempfile.TemporaryFile:

import tempfile

def make_temp_file():
    """Create a temporary file. Best used with a context manager"""
    tmpfile = tempfile.NamedTemporaryFile()
    return tmpfile

Is this safe as is or should this be wrapped with a context manager?

import tempfile
from contextlib import contextmanager

@contextmanager
def make_temp_file():
    """Create a temporary file. Best used with a context manager"""
    tmpfile = tempfile.NamedTemporaryFile()
    return tmpfile

My confusion comes from the linter pylint who still insist the first example triggers a consider-using-with rule.

Asked By: Keto

||

Answers:

Your function already returns a context object (something with __enter__ and __exit__ methods) and does not need to be rewrapped. That wouldn’t change anything.

The idea of with is that the context manager’s __exit__ will be called when the with suite finishes, even if there is an error. But algorithms don’t always fit conveniently into suites as your code indicates. If you can’t use a context manager, you need some other mechanism to ensure that something closes the object.

As an example, suppose your function performed some other task before return. It could hit an exception and terminate before anybody has a chance to close the object. In that case you would do

def make_temp_file():
    tmpfile = tempfile.NamedTemporaryFile()
    try:
        do_other_things()
    except:
        tmpfile.close()
        raise

If your function doesn’t do anything after creating the object, you can skip the intermediate variable and likely get rid of the lint warning while you are at it. From the pylint doc the warning is suppressed when the call result is returned from the enclosing function.

def make_temp_file():
    return tempfile.NamedTemporaryFile()

Note that since you return a context object that needs to be closed, the same issues apply to the thing that calls the function. Either that should be a with or should have some other mechanism to make sure the object is closed when done.

Answered By: tdelaney