I'm comparing these two pieces of code that could appear to do the same thing. The first is likely the recommended approach, and it does a bit more than one might assume.
import tempfile
with tempfile.NamedTemporaryFile() as f:
f.write(b"test")
and
import tempfile, os
fd, name = tempfile.mkstemp()
with open(fd, "wb") as f:
f.write(b"test")
os.remove(name)
Watching these execute with strace and comparing the result, it's clear that they functionally do the same job in this simple case, but there are some details that differ.
Looking at the strace output, they both load libaries and do some testing, and finally get to executing the intent of the code. The system calls seen can be summarised to something like this.
openat(AT_FDCWD, "<tempname>", ...) = 3 # BOTH
ioctl(3, FIOCLEX) = 0 # Only NamedTemporaryFile
newfsstatat(3, ....) = 0 # BOTH
ioctl(3, TCGETS, ...) = -1 ENOTTY # BOTH
lseek(3, 0, SEEK_CUR) = 0 # BOTH
lseek(3, 0, SEEK_CUR) = 0 # Only NamedTemporaryFile
write(3, "test", 4) = 4 # BOTH
lseek(3, 0, SEEK_CUR) = 4 # Only NamedTemporaryFile
close(3) = 0 # BOTH
unlink("<tempname>") = 0 # BOTH
So the NamedTemporaryFile variant sets the close-on-exec flag on the file descriptor, and also appears to keep track of the file offset at all times. I can speculate about why this is and possible relation to thread safety or something like that, but is this behaviour documented somewhere, with a description of why this behaviour is helpful in various circumstances?