skip to content

When do you use tempfile.NamedTemporaryFile, TemporaryDirectory or mkstemp?

level: juniorimportance: must knowfreq 58%

answer

  1. Three doors into the same scratch space
  2. One cleans up, one does not
  3. Directory, file object, or raw descriptor
  4. mkstemp hands back an fd and a path
  5. delete_on_close arrived in 3.12

basics

~10 s

tempfile.TemporaryDirectory gives a whole scratch directory removed when its with-block ends. tempfile.NamedTemporaryFile gives one auto-deleted open file. tempfile.mkstemp gives a raw file descriptor plus a path you must close and unlink yourself.

solid answer

~40 s

All three create securely-named entries under `tempfile.gettempdir()`, and they differ in lifetime and in what you get back. `tempfile.TemporaryDirectory()` is a context manager that yields a directory path and removes the whole tree on exit — the right choice when a step writes several files or when a tool wants a directory to work in. `tempfile.NamedTemporaryFile()` returns an already-open file object whose `.name` is a real path, and it deletes itself when it closes; since Python 3.12 `delete_on_close=False` lets you close it, reopen it by name, and still have it removed at block exit, which is what makes the write-then-reopen pattern work on Windows. `tempfile.mkstemp()` is the low-level primitive: it returns `(fd, path)` and cleans up nothing, so you own the `os.close` and the `os.unlink`. Never use `tempfile.mktemp()`.

code

python · 8 lines
python
import pathlib
import tempfile

with tempfile.TemporaryDirectory(prefix="recon-") as d:
    scratch = pathlib.Path(d) / "batch.csv"
    scratch.write_text("id,amount\n1,10.00\n", encoding="utf-8")
    print(scratch.read_text(encoding="utf-8").splitlines()[0])
print("tree still there:", pathlib.Path(d).exists())

go deeper

for a junior

Be ready to write the three-line with tempfile.TemporaryDirectory() as d: pattern from memory and say what happens to the directory afterwards. Knowing that tempfile picks the location and the name for you is the core recall here.

for a middle

Explain the mechanics: what each helper returns, that mkstemp hands back an OS file descriptor with no cleanup attached, and that the file object's .name is a real path. Name the Windows reopen problem and the 3.12 delete_on_close fix.

for a senior

Show the operational judgement: putting a run's whole scratch under one directory so cleanup is one call, checking free space before large writes, and accepting that a hard kill leaves stragglers a service must sweep itself.

for a principal

Own the policy question of where scratch space lives across a fleet — a RAM-backed temp mount versus a sized disk volume, whether jobs share a temp root at all, and what the retention and sweep story is when nothing runs a cleanup on crash.

## The problem `tempfile` solves Every non-trivial program eventually needs scratch space on disk: a file to stream a large download into, a directory for a tool that only knows how to read from a directory, a staging file that becomes the real output later. Hand-rolling that as `open("/tmp/job.csv", "w")` is wrong for three separate reasons. The system temp directory is shared and world-writable, so another user's process can interfere with a name you chose. The name is fixed, so two runs of your own program collide. And nothing removes the file, so the directory fills up over months. `tempfile` addresses all three, and it offers three shapes depending on how much control you need. ## `TemporaryDirectory` — a whole scratch tree `tempfile.TemporaryDirectory()` creates a fresh directory with permissions `0o700` and a random name. Used as a context manager, entering it returns the directory path as a `str`; leaving it removes the entire tree with `shutil.rmtree`. It also exposes a `cleanup()` method for non-`with` use, and it registers a finalizer so the tree still goes away if the object is simply garbage-collected. Two keyword arguments matter in production. Since Python 3.10, `ignore_cleanup_errors=True` swallows failures during that removal — valuable on Windows, where a file another process still has open cannot be deleted and would otherwise raise from the `__exit__`. Since Python 3.12, `delete=False` keeps the tree on disk while still giving you the context-manager shape, which is how you leave evidence behind for a post-mortem without restructuring the code. This is the default choice whenever a step produces more than one artefact, because cleanup becomes one operation over one root instead of bookkeeping per file. ## `NamedTemporaryFile` — one file with a usable name `tempfile.NamedTemporaryFile()` returns an already-open file object, default mode `'w+b'`, whose `.name` attribute is a genuine path on the filesystem. When it is closed — explicitly or by leaving its `with` block — it deletes itself. The classic wrinkle is Windows: an open file cannot be opened a second time by name there, so the natural pattern "write it, close nothing, hand `.name` to another component" fails on that platform. Python 3.12 added the `delete_on_close` parameter for exactly this. With `delete=True, delete_on_close=False`, `close()` no longer deletes; removal is deferred to the context-manager exit, so you can close, reopen by name, read it back, and still get automatic cleanup. Two relatives round out the family. `tempfile.TemporaryFile()` has no usable name at all and is the cheapest option when only your own process will read the data. `tempfile.SpooledTemporaryFile()` keeps content in memory until it exceeds a `max_size` and only then rolls over to disk. ## `mkstemp` — the low-level primitive `tempfile.mkstemp()` returns a two-tuple `(fd, path)`. The `fd` is an operating-system file descriptor opened with the exclusive-create flags and mode `0o600`; the `path` is absolute. Nothing is registered for cleanup. You wrap the descriptor with `os.fdopen` (or close it with `os.close`) and you call `os.unlink` yourself, normally in a `finally`. Reach for it when the file must outlive the object that made it, or when you need the descriptor rather than a Python file object — for example a file you hand to a child process, or one whose deletion is conditional on success. `tempfile.mkdtemp()` is the same idea for directories: it returns a path and cleans up nothing. ## Choosing, in one pass - Several files, or a directory some tool writes into → `TemporaryDirectory`. - One file, automatic cleanup, name occasionally needed → `NamedTemporaryFile`. - A file whose lifetime you control explicitly → `mkstemp`. - `tempfile.mktemp()` → never; it returns a name without creating anything and is unsafe. ## Where the files land `tempfile.gettempdir()` resolves the base directory from the `TMPDIR`, `TEMP` and `TMP` environment variables, then platform defaults, and caches the result. Every helper above accepts a `dir=` argument to override it per call. Overriding is worth doing when the default temp filesystem is a small RAM-backed mount that a multi-gigabyte scratch file would exhaust, or when you intend to rename the file into its final directory afterwards and want to stay on one filesystem. `shutil.disk_usage(path)` returns a named tuple of `total`, `used` and `free` bytes and is the cheap way to check before writing something large. ## Cleanup is a discipline, not a guarantee Context managers cover normal exits and exceptions. They do not cover a hard kill or a power loss, so a long-lived service that creates temp files should still expect stragglers and sweep its own scratch directory on startup — which is much easier when everything it writes lives under one directory it created itself.

  • Why does handing a NamedTemporaryFile's .name to another component fail on Windows, and how do you fix it?
    Windows refuses to open a file a second time by name while the first handle is still open, so the other component gets a permission error. Since Python 3.12 the fix is `delete_on_close=False` with `delete=True`: close the object so the handle is released, let the other component open the path, and let the context-manager exit do the deletion. On earlier versions you used `delete=False` and unlinked by hand.
  • What does tempfile.gettempdir() actually consult, and when would you override it?
    It checks the `TMPDIR`, `TEMP` and `TMP` environment variables, then platform-specific defaults, and caches the answer for the process. You override it with the `dir=` argument (or by setting `tempfile.tempdir`) when the default mount is a small RAM-backed filesystem that a large scratch file would fill, or when you need the temp file on the same filesystem as its eventual destination.
  • If a process is killed hard, what cleans up its temporary files?
    Nothing in Python does. Context managers and finalizers only run on ordinary exits and exceptions; a `SIGKILL`, an OOM kill or a power loss leaves the files behind. That is an argument for putting everything a run creates under one `TemporaryDirectory` and for having long-lived services sweep their own scratch root at startup, rather than relying on the operating system's temp reaper.

TemporaryDirectory is a rented workshop that is swept out when you hand back the key; NamedTemporaryFile is a single sheet of scratch paper that shreds itself; mkstemp is being handed the raw key and told the cleaning is yours.

saying these in an interview costs you the question

  • Thinks mkstemp deletes the file automatically
  • Believes tempfile.mktemp is a safe modern alternative
  • Says NamedTemporaryFile returns a path string, not a file object
  • Assumes a temp file survives being reopened by name on Windows
  • Hardcodes /tmp instead of asking tempfile where temp space is
  • Uses TemporaryDirectory but keeps references to files after the block

context