Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to leave a `with` block without closing the resource?

I am trying to achieve something similar to

from tempfile import TemporaryFile

def open_head(file_path):
   with open(file_path, 'r') as f,
        TemporaryFile() as tf:
       for i in range(0,10):
           tf.write(f.read_line())
       return tf

such that the caller gets ownership of the temporary file.

In particular, I don't want the with statement to close the TemporaryFile. But if anything goes wrong before the return, I still want the TemporaryFile be closed by the with statement.

Ideally, I would then want to write the caller as

with open_head(file_path):
    # more code here

Is this possible somehow? E.g. by writing return do_not_close(tf) or some other utility functionality?

Or am I approaching this completely wrong and there is a more Pythonic way to return a TemporaryFiles or other resources, between functions while guaranteeing exception safety?

like image 779
Vogelsgesang Avatar asked Sep 05 '25 16:09

Vogelsgesang


2 Answers

You don't. open_head should take an already opened handle, which the caller is responsible for closing.

from tempfile import TemporaryFile
from itertools import islice


def head(file_path, fh):
    with open(file_path) as f:
        for line in islice(f, 10):
            fh.write(line)


with TemporaryFile() as tf:
    head(file_path, tf)
    # Do other stuff with tf before it gets closed.
    

In general, anytime you are opening a file in an function, ask yourself if you can push the actual open up to the caller and accept a file-like object instead. Aside from making your code more reusable, it makes your code easier to test. head doesn't have to be called with an actual file: it can be called with any file-like object, such as io.StringIO.


Put another way: the with statement enforces the advice

If you open the file, you are responsible for closing it as well.

The contrapositive of that advice is

If you aren't responsible for closing the file, you aren't responsible for opening the file, either.

like image 145
chepner Avatar answered Sep 07 '25 16:09

chepner


Just move the TemporaryFile outside the context manager and wrap it in a try except block

from tempfile import TemporaryFile

def open_head(path: str):
    try:
        tf = TemporaryFile()
        with open(path, "r") as f:
            for _ in range(10):
                tf.write(f.readline())
            return tf
    except Exception as e:
        tf.close()
        raise e
like image 35
Jacob Avatar answered Sep 07 '25 16:09

Jacob