How to create a directory and its missing parents in Python
Creating a directory is one call. Creating one several levels deep, without failing when it already exists and without a race condition, needs two keyword arguments that are easy to forget.
The answer
from pathlib import Path
Path("/tmp/a/b/c").mkdir(parents=True, exist_ok=True)
exists : True
is_dir : True
Both keywords earn their place. Without parents=True a missing intermediate
directory is an error:
mkdir() with a missing parent -> FileNotFoundError: [Errno 2] No such file or directory: '/tmp/demo0049/x/y'
And without exist_ok=True an existing directory is an error:
mkdir(parents=True) on an existing dir -> FileExistsError: [Errno 17] File exists: '/tmp/demo0049/a/b/c'
The os equivalent is
os.makedirs(path, exist_ok=True), where makedirs already implies
parents=True. Both are fine;
pathlib reads better once
you are also joining paths and reading files.
Why not to check first
The version you will write without thinking about it:
if not path.exists():
path.mkdir()
Between the exists() and the mkdir(), another process can create the
directory — and then your mkdir() raises the very error you were trying to
avoid. It is a check-then-act race, and it will fail rarely enough to be
puzzling.
exist_ok=True pushes the whole thing into one system call, where the kernel
settles it. Same idea as try/except over if exists: do the thing and
handle the failure, rather than asking a question whose answer expires
immediately.
exist_ok=True does not mean “if anything is there”
Worth knowing, because the name suggests otherwise. Put a file at the path:
a FILE at the path, mkdir(exist_ok=True) -> FileExistsError: [Errno 17] File exists: '/tmp/demo0049/clash'
exist_ok=True tolerates an existing directory. Anything else at that path
is still an error — which is the right call, and worth knowing if you are
tempted to treat it as “make sure this path is usable”.
Permissions, and the two surprises
mkdir() takes a mode, and the value you pass is not the value you get:
mkdir(mode=0o700) -> drwx------
mkdir(mode=0o777) -> drwxr-xr-x
current umask : 0o22
The mode is masked by the process umask, so 0o777 with the usual 0o022
umask produces 0o755. This is standard Unix behaviour rather than a Python
quirk, but it catches people who set a mode and then check it.
The second surprise is more specific:
with parents=True, the PARENT gets the default mode, not yours:
parent drwxr-xr-x inner drwx------
Created in one call with mode=0o700, parents=True, only the leaf got the
mode. The parents were created with the default. If you are building a tree
that is meant to be private, the intermediate directories are not — and nothing
warns you.
Set the modes explicitly afterwards if it matters, with
Path.chmod.
The case you actually have
Nine times in ten, you are about to write a file and its directory may not exist:
outfile = Path("reports") / "2026" / "out.txt"
outfile.parent.mkdir(parents=True, exist_ok=True)
outfile.write_text("done\n")
.parent is the whole trick — you have the file path, and you need the
directory containing it.
When you want it gone again
If the directory is temporary, let Python handle it:
with tempfile.TemporaryDirectory() as tmp:
...
inside the with block : True
after the with block : False
tempfile.TemporaryDirectory
removes the tree on exit, including on an exception. Much better than a
hand-rolled path in /tmp that survives every crash.
Removing by hand has one asymmetry worth remembering:
Path.rmdir() only removes an EMPTY directory:
-> OSError: [Errno 39] Directory not empty
rmdir() refuses a non-empty directory.
shutil.rmtree
removes the whole tree without asking, which is exactly as dangerous as it
sounds.
About Netcup (advertisement)
The German host Netcup offers, among other things, affordable and powerful web hosting packages, KVM-based root servers and dedicated servers. With our voucher codes you can save even more (6€ off your first order, 30% off all KVM-based root servers, ...).