未翻訳のページ
このページはまだ日本語に翻訳されていないため、英語の原文を表示しています。 翻訳に協力する
tempfile Module Complexity¶
The tempfile module creates files and directories that nobody else can have claimed first,
and can remove them when you are done. Creating one is a loop: draw a random name, try to create
it exclusively, and draw again if the name is taken. Everything after that is ordinary file I/O
on the object you get back.
r is the name attempts one creation makes: 1 unless randomly drawn names collide with entries
already in the directory, and never more than tempfile.TMP_MAX. c is the candidate
directories the first gettempdir() probes, n is the bytes (characters, in text mode) a file
holds, k is the bytes one read or write moves, and s is a SpooledTemporaryFile's
max_size. m is the entries in a temporary directory's tree at cleanup, d its depth and w
the entries in its largest directory. One filesystem call - a create, a stat, an unlink - is
priced at O(1). A creation with dir=None also pays gettempdir()'s one-time probe on the
first use in the process.
Complexity Reference¶
Creating files and directories¶
| Operation | Time | Space | Notes |
|---|---|---|---|
tempfile.mkstemp(suffix=None, prefix=None, dir=None, text=False) |
O(r) | O(1) | One exclusive os.open per attempt; returns an open descriptor and a path, both the caller's to close and remove |
tempfile.mkdtemp(suffix=None, prefix=None, dir=None) |
O(r) | O(1) | One os.mkdir per attempt; the caller removes the directory |
tempfile.mktemp(suffix='', prefix='tmp', dir=None) |
O(r) | O(1) | Deprecated and unsafe: one lstat per attempt and nothing is created, so the name can be taken before you use it |
NamedTemporaryFile¶
| Operation | Time | Space | Notes |
|---|---|---|---|
tempfile.NamedTemporaryFile(mode='w+b', buffering=-1, encoding=None, newline=None, suffix=None, prefix=None, dir=None, delete=True, *, errors=None, delete_on_close=True) |
O(r) | O(1) | Created as mkstemp() does; the data lives on disk, so memory is the file buffer |
NamedTemporaryFile.name, NamedTemporaryFile.file |
O(1) | O(1) | The path, visible in the directory while the file is open, and the underlying file object |
| Writing k bytes | O(k) | O(1) binary, O(k) text | Buffered file I/O; in text mode the string is encoded to bytes first |
| Reading k bytes | O(k) | O(k) | The result is the only thing held |
close(), leaving the with block |
O(1) | O(1) | One unlink when delete=True; with delete_on_close=False (Python 3.12+) closing keeps the file and leaving the with block removes it |
TemporaryFile¶
| Operation | Time | Space | Notes |
|---|---|---|---|
tempfile.TemporaryFile(mode='w+b', buffering=-1, encoding=None, newline=None, suffix=None, prefix=None, dir=None, *, errors=None) |
O(r) | O(1) | On Linux, where the filesystem supports O_TMPFILE, no name is drawn at all; otherwise on POSIX the file is created under a name and unlinked at once. On Windows this is NamedTemporaryFile |
| Reading and writing | O(k) | O(k) to read; to write, O(1) binary and O(k) text | As for NamedTemporaryFile; on POSIX there is no name to see |
SpooledTemporaryFile¶
| Operation | Time | Space | Notes |
|---|---|---|---|
tempfile.SpooledTemporaryFile(max_size=0, mode='w+b', buffering=-1, encoding=None, newline=None, suffix=None, prefix=None, dir=None, *, errors=None) |
O(1) | O(1) | Starts as an in-memory buffer and creates no file |
SpooledTemporaryFile.write(s) |
O(k) | O(k) in memory, O(1) once rolled over | The write that takes the position past max_size also pays rollover(), once; max_size=0 never rolls over on its own |
SpooledTemporaryFile.writelines(lines) |
O(n) | O(s + k) for a positive max_size, O(n) for max_size=0 |
k = the longest line. Rolls over as soon as a line crosses max_size; 3.10, 3.11, 3.12.0-3.12.9 and 3.13.0-3.13.2 hold the whole iterable in memory first, O(n) |
SpooledTemporaryFile.rollover() |
O(r + n) | O(1) | Creates a TemporaryFile, copies the n held bytes to it and frees the in-memory buffer; does nothing once rolled over |
SpooledTemporaryFile.fileno() |
O(r + n) first call, then O(1) | O(1) | Needs a real descriptor, so it forces rollover() |
SpooledTemporaryFile.truncate(size=None) |
O(r + n) if size > max_size, else O(1) |
O(1) | A size past max_size forces rollover() first |
SpooledTemporaryFile.read(size), read1(), readinto(b), readinto1(b), readline(), readlines(), iterating |
O(k) | O(k) | Passed to the in-memory buffer or the file, whichever holds the data; k = what is returned. read1() and the readinto pair are Python 3.11+ |
SpooledTemporaryFile.seek(), tell(), flush(), close(), detach(), isatty(), readable(), seekable(), writable() |
O(1) | O(1) | Passed through as well; detach(), readable(), seekable() and writable() are Python 3.11+ |
SpooledTemporaryFile.closed, mode, name, encoding, errors, newlines |
O(1) | O(1) | name is None until the file rolls over |
TemporaryDirectory¶
| Operation | Time | Space | Notes |
|---|---|---|---|
tempfile.TemporaryDirectory(suffix=None, prefix=None, dir=None, ignore_cleanup_errors=False, *, delete=True) |
O(r) | O(1) | mkdtemp() plus a finalizer that cleans up if the object is collected |
TemporaryDirectory.name |
O(1) | O(1) | The path; with binds this string, not the object |
TemporaryDirectory.cleanup(), leaving the with block |
O(m) | O(d·w) | shutil.rmtree over the tree; a PermissionError makes it reset permissions and retry, and ignore_cleanup_errors=True leaves what still cannot be removed instead of raising. A second call does nothing; delete=False (Python 3.12+) skips it on exit |
Locations and constants¶
| Operation | Time | Space | Notes |
|---|---|---|---|
tempfile.gettempdir() |
O(c) first call, then O(1) | O(c) first call, then O(1) | Probes TMPDIR, TEMP, TMP, the platform's usual directories and the working directory by creating and deleting a file in each, and caches the first that works |
tempfile.gettempdirb() |
O(c) first call, then O(1) | O(c) first call, then O(1) | The same directory, and the same cache, as bytes |
tempfile.gettempprefix(), tempfile.gettempprefixb() |
O(1) | O(1) | 'tmp' and b'tmp' |
tempfile.tempdir |
O(1) | O(1) | The cache itself; assign it before first use to skip the probe |
tempfile.TMP_MAX |
O(1) | O(1) | The ceiling on r; exhausting it raises FileExistsError |
Creating Temporary Files¶
Named vs Unnamed Files¶
A NamedTemporaryFile has a path other code can open, and it is removed when it closes.
On POSIX a TemporaryFile has no path at all, which on Linux means it can skip naming altogether.
Neither holds its data in memory.
import os
import tempfile
with tempfile.TemporaryDirectory() as workdir:
with tempfile.NamedTemporaryFile(dir=workdir) as named: # O(r)
payload = b'x' * 100_000
named.write(payload) # O(k) time, O(1) more memory - it goes to disk
named.flush()
assert os.listdir(workdir) == [os.path.basename(named.name)]
assert os.path.getsize(named.name) == 100_000
assert os.listdir(workdir) == [] # unlinked on exit - O(1)
with tempfile.TemporaryFile(dir=workdir) as unnamed: # O(r), no name at all on Linux
unnamed.write(b'data')
unnamed.seek(0)
assert unnamed.read() == b'data' # O(k)
if os.name == 'posix':
assert os.listdir(workdir) == [] # nothing to see, even while open
Keeping What Would Be Deleted¶
Three switches hand removal back to you. delete=False keeps a NamedTemporaryFile for good.
delete_on_close=False keeps it through close(), so another opener can use the path, and
removes it when the with block ends. TemporaryDirectory(delete=False) skips the cleanup on
exit. The last two need Python 3.12.
import os
import tempfile
with tempfile.TemporaryDirectory() as workdir:
with tempfile.NamedTemporaryFile('w', dir=workdir, delete_on_close=False) as f:
f.write('config')
f.close() # O(1) - the file stays
with open(f.name) as reader:
assert reader.read() == 'config'
assert not os.path.exists(f.name) # removed on exit
kept = tempfile.NamedTemporaryFile(dir=workdir, delete=False) # O(r)
kept.close()
assert os.path.exists(kept.name) # still there: removing it is your job
os.unlink(kept.name) # O(1)
held = tempfile.TemporaryDirectory(dir=workdir, delete=False) # O(r)
with held as name:
pass
assert os.path.isdir(name) # left in place
held.cleanup() # an explicit call still removes it
assert not os.path.exists(name)
mkstemp and mkdtemp¶
The low-level pair return what they made and forget about it. Both try names until one is free, so a crowded directory costs extra attempts, never a scan of what is already there.
import os
import tempfile
workdir = tempfile.mkdtemp(prefix='job_') # O(r)
assert os.path.basename(workdir).startswith('job_')
assert os.path.isabs(workdir)
fd, path = tempfile.mkstemp(suffix='.txt', dir=workdir) # O(r)
os.write(fd, b'payload')
os.close(fd)
assert os.path.dirname(path) == workdir and path.endswith('.txt')
os.unlink(path) # nothing removes them for you
os.rmdir(workdir)
Why mktemp Is Unsafe¶
mktemp() checks that a name is free and returns it without creating anything. Whatever
creates the file later is racing every other process for that name; mkstemp() creates it in
the same step that checks it.
import os
import tempfile
with tempfile.TemporaryDirectory() as workdir:
name = tempfile.mktemp(dir=workdir) # O(r) - one lstat per attempt, nothing created
assert not os.path.exists(name)
open(name, 'w').close() # another process gets there first
try:
os.open(name, os.O_CREAT | os.O_EXCL | os.O_WRONLY)
except FileExistsError:
pass
else:
raise AssertionError('the name was still free')
Spooling in Memory¶
A SpooledTemporaryFile keeps what you write in memory until the position passes max_size.
The write that crosses it calls rollover(), which creates a real temporary file and copies
everything written so far into it - O(n) once - and from then on memory stays flat. With the
default max_size=0 it never rolls over on its own.
import tempfile
with tempfile.TemporaryDirectory() as workdir:
with tempfile.SpooledTemporaryFile(max_size=1_000, dir=workdir) as spool: # O(1)
spool.write(b'x' * 1_000) # O(k), held in memory
assert spool.name is None # still no file
spool.write(b'y') # past max_size: rollover() copies all 1,001 bytes - O(n)
assert spool.name is not None
spool.seek(0)
assert spool.read() == b'x' * 1_000 + b'y'
with tempfile.SpooledTemporaryFile(dir=workdir) as unbounded: # max_size=0
unbounded.write(b'z' * 100_000)
assert unbounded.name is None # everything is still in memory
unbounded.fileno() # O(n) - a descriptor forces rollover()
assert unbounded.name is not None
Temporary Directories¶
TemporaryDirectory is mkdtemp() plus cleanup. Leaving the with block removes the whole
tree with shutil.rmtree, so the exit costs what you put inside, and it runs even when the block
raises.
import os
import tempfile
with tempfile.TemporaryDirectory() as parent:
with tempfile.TemporaryDirectory(dir=parent) as workdir: # O(r)
for index in range(100):
with open(os.path.join(workdir, f'chunk{index}'), 'w') as f:
f.write('x')
os.mkdir(os.path.join(workdir, 'nested'))
assert len(os.listdir(workdir)) == 101
assert not os.path.exists(workdir) # O(m) - all 101 entries removed
try:
with tempfile.TemporaryDirectory(dir=parent) as workdir:
open(os.path.join(workdir, 'partial'), 'w').close()
raise RuntimeError('job failed')
except RuntimeError:
pass
assert not os.path.exists(workdir) # cleaned up anyway
The Default Directory¶
gettempdir() works out where temporary files go by trying to create a file in each candidate
directory in turn. It does that once per process and keeps the answer in tempfile.tempdir,
which every later call - and every creation with dir=None - reads.
import os
import tempfile
first = tempfile.gettempdir() # O(c) the first time
assert tempfile.tempdir == first # the cached answer
assert tempfile.gettempdir() == first # O(1) afterwards
assert tempfile.gettempdirb() == os.fsencode(first) # the same cache, as bytes
assert tempfile.gettempprefix() == 'tmp' # O(1)
assert tempfile.gettempprefixb() == b'tmp'
Common Patterns¶
Replacing a File Atomically¶
Write the new contents to a temporary file in the target's own directory, then rename it over the target. The rename is O(1), and on POSIX readers see either the old file or the new one, never half of it.
import os
import tempfile
with tempfile.TemporaryDirectory() as workdir:
target = os.path.join(workdir, 'settings.txt')
with open(target, 'w') as f:
f.write('old')
with tempfile.NamedTemporaryFile('w', dir=workdir, delete=False) as tmp: # O(r)
tmp.write('new') # O(k)
os.replace(tmp.name, target) # O(1) - same filesystem, so a rename
with open(target) as f:
assert f.read() == 'new'
assert os.listdir(workdir) == ['settings.txt']
Buffering Uploads of Unknown Size¶
import tempfile
chunks = [b'a' * 64_000 for _ in range(10)]
with tempfile.TemporaryDirectory() as workdir:
# Small bodies stay in memory; large ones go to disk after one O(n) copy
with tempfile.SpooledTemporaryFile(max_size=256_000, dir=workdir) as body:
body.writelines(chunks) # O(n) - rolls over once past max_size
assert body.name is not None
body.seek(0)
assert len(body.read()) == 640_000 # O(k)
Performance Best Practices¶
✅ Do:
- Use
TemporaryFilewhen nothing else needs the path: on Linux it skips naming entirely - Pick a
max_sizeforSpooledTemporaryFile, so memory is capped and only large files pay the rollover copy - Create the temporary file in the target's directory when you will rename it into place:
os.replace()is an O(1) rename only within one filesystem - Use a context manager, so cleanup runs even when the block raises
❌ Avoid:
mktemp()- it returns a name another process can take before you create itSpooledTemporaryFile()with the defaultmax_size=0for data of unknown size - it never leaves memory unless something forcesrollover()
Version Notes¶
- Python 3.10+:
TemporaryDirectoryacceptsignore_cleanup_errors - Python 3.11+:
SpooledTemporaryFileimplements the fullio.BufferedIOBaseorio.TextIOBaseinterface, addingread1(),readinto(),readinto1(),detach(),readable(),seekable()andwritable() - Python 3.12+:
NamedTemporaryFileacceptsdelete_on_close,TemporaryDirectoryacceptsdelete, andmkdtemp()always returns an absolute path - Python 3.12.10+, 3.13.3+:
SpooledTemporaryFile.writelines()rolls over as soon as a line crossesmax_size; earlier 3.12 and 3.13 releases, and all of 3.10 and 3.11, hold the whole iterable in memory before checking - Python 3.13.13+, 3.14.4+:
TMP_MAXis 20, so a creation gives up after 20 collisions; earlier releases use the platform'sos.TMP_MAX, which is far larger - All Python 3:
mktemp()is deprecated but issues no warning