Hands-on tempfile modes lab: TemporaryFile vs NamedTemporaryFile delete True/False vs mkstemp unlink patterns with real measured ops/s on localhost box.
6 min read
Need a scratch file? TemporaryFile, NamedTemporaryFile(delete=...), and mkstemp are not the same syscall story. One is often already anonymous; another keeps a visible path; fsync turns a cheap unlink into a barrier.
This is a hands-on lab with measured numbers:
NamedTemporaryFile(delete=True) vs delete=False + manual unlink.mkstemp unlink-while-open vs close-then-unlink (± fsync).Related links:
Lab honesty (1 Oct 2026 IST): Python 3.13.5; dir under /tmp overlay. Affiliates: 0.
Verdict up front: TemporaryFile ~41100 ops/s; NamedTemporaryFile ~19600 (~0.48×). mkstemp+fsync+unlink ~3600; without fsync ~32k.
| API | Path visible? | Cleanup |
|---|---|---|
TemporaryFile | usually no (POSIX unlinked) | on close |
NamedTemporaryFile(delete=True) | yes while open | unlink on close |
NamedTemporaryFile(delete=False) | yes | you unlink |
mkstemp + unlink while open | no after unlink | anonymous fd |
mkstemp + close + unlink | briefly yes | manual |
same + fsync | — | durable tax |
Related links:
Script: lab-evidence/45-tempfile-modes/results/run_lab.py.
| Arm | ops/s | µs/op | vs TemporaryFile |
|---|---|---|---|
| TemporaryFile | 41103 | 24.3 | 1.00× |
| mkstemp leave file (no unlink timed) | 41174 | 24.3 | 1.00× |
| mkstemp close+unlink no fsync | 32502 | 30.8 | 0.79× |
| mkstemp unlink-while-open | 31755 | 31.5 | 0.77× |
| NamedTemporaryFile delete=False+unlink | 20174 | 49.6 | 0.49× |
| NamedTemporaryFile delete=True | 19563 | 51.1 | 0.48× |
| mkstemp close+unlink + fsync | ~3600 | ~278 | ~0.09× |
Payload spot checks (NamedTemporaryFile delete=True): 64 B ~20835/s; 64 KiB ~14979/s.
Related links:
TemporaryFile (fastest convenient API here).NamedTemporaryFile (~2× slower) or mkstemp.TemporaryFile or SpooledTemporaryFile.NamedTemporaryFile(delete=False) + try/finally unlink, or mkstemp.os.replace target.delete=False.Related links:
Each op writes a fixed payload, flushes, and closes. NamedTemporaryFile(delete=False) unlinks after close inside the timed op. The fsync mkstemp arm calls os.fsync(fd) before close. Leftover files from the “leave file” arm are unlinked outside the timed window.
tempfile / os/tmp on overlay lab VMAnonymous temps are the speed default; named temps buy a path for about half the throughput here.
Throughput nearly matched (~19.6k vs ~20.2k). The product difference is failure mode: delete=False survives across process crashes until something unlinks the path — useful for debugging postmortems, dangerous for /tmp fill-ups. Prefer delete=True unless a child process must reopen by name after you close (then pass the name carefully and unlink in a finally).
Named temps often feed os.replace into a config path (atomic-rename lab). Create the named temp in the destination directory so replace stays same-FS atomic. Using TemporaryFile cannot supply that path; that is the feature trade, not a bug.
NamedTemporaryFile delete=True 0.48× · delete=False+unlink 0.49× · mkstemp unlink-while-open 0.77× · mkstemp close+unlink no fsync 0.79× · mkstemp+fsync ~0.09×
If a profile shows temp creation hot, switching Named→Temporary (when safe) is a plausible 2×; adding fsync is how you accidentally get a 10× regression.
On busy shared /tmp, also watch inode limits and sticky-bit permissions — throughput numbers assume a quiet lab workdir.
For small blobs that might grow, SpooledTemporaryFile keeps bytes in RAM until a size threshold, then rolls to disk. That path can beat any of today’s disk arms for sub-threshold sizes — and looks nothing like NamedTemporaryFile in profiles. If your “temp file” is usually under a few KiB, consider spooling before paying named-path costs.
Named temps use predictable directory listings if permissions are wrong. Keep dir= on a private directory when secrets touch disk. TemporaryFile’s anonymous POSIX pattern reduces the rename/race window because there is often no lasting directory entry — another reason it is the default scratch tool.
At ~20k NamedTemporaryFile ops/s, one core could theoretically open ~20 million named temps per second in a microbench — reality will be lock contention on the directory and page cache. Still, if a job creates 100k temps, budget ~5 s of create/close overhead from this lab’s µs/op before counting useful work. TemporaryFile would be ~2.4 s for the same count on these numbers.
TemporaryFile (~41k ops/s) beat NamedTemporaryFile (~20k, ~0.48×) for small write/close cycles. mkstemp without fsync sat ~32k; with fsync ~3.6k. Pick anonymous when you can; pay for names deliberately; fsync only with a durability story.
Evidence: lab-evidence/45-tempfile-modes/results/. Affiliates: 0.
Lab evidence
Lab 1 Oct 2026 IST. Python 3.13.5; /tmp; ~400 B payload. TemporaryFile ~41103 ops/s; NamedTemporaryFile(delete=True) ~19563 (~0.48x); delete=False+unlink ~20174; mkstemp unlink-while-open ~31755; mkstemp close+unlink (no fsync) ~32502; mkstemp+fsync+unlink ~3600. Affiliates: 0. Evidence: lab-evidence/45-tempfile-modes/.
Hands-on fnmatch.filter vs re.compile name-list filtering measured on Linux localhost.
Lab1 Oct 20264 min read
Hands-on tarfile vs zipfile create+extract lab: real MB/s on a mixed small-file fixture (uncompressed tar vs zip), measured on Linux localhost for SREs.
Lab1 Oct 20264 min read
Hands-on os.scandir vs listdir vs Path.iterdir lab: real entries/s for names and is_file on a synthetic tree, measured on Linux localhost (lab) for SREs.
Lab1 Oct 20264 min read