How to Recursively Create Directories

by Christoph Schiessl on Python

There are many ways to create directories in Python, but my favorite is the pathlib module from the standard library. The centerpiece of this module is the Path class, which presents an arbitrary location in a file system.

Before you can create a directory, you need to construct a Path object pointing to the future location of your directory. There are no mandatory parameters to instantiate the Path class, so if you create a Path object without parameters, it is equivalent to Path("."), where the . represents your current working directory.

Python 3.12.4 (main, Jun 14 2024, 13:21:39) [GCC 14.1.1 20240522] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from pathlib import Path
>>> Path()
PosixPath('.')
>>> Path(".")
PosixPath('.')
>>> assert Path() == Path(".")

You can use the static Path.cwd() method to get the current working directory of your Python process.

>>> Path.cwd()
PosixPath('/home/cs/Code/bugfactory.io')
>>> Path(".").absolute()
PosixPath('/home/cs/Code/bugfactory.io')
>>> assert Path(".").absolute() == Path.cwd()

As you can see, Path(".") and Path.cwd() refer to the same file system location (i.e., no AssertionError is raised). Also, note that you can use the absolute() method to convert any relative path to an absolute one, using your current working directory as the basis.

Now, if you pass one or more parameters to Path(), it will give you a location below your current working directory.

>>> (normal := Path("normal")).absolute()
PosixPath('/home/cs/Code/bugfactory.io/normal')
>>> (nested := Path("nested", "foobar")).absolute()
PosixPath('/home/cs/Code/bugfactory.io/nested/foobar')

Neither of these currently exists in the file system, and we can assert this using the Path.exists() method.

>>> assert not normal.exists()
>>> assert not nested.exists()

Normal Directory Creation

Now, to finally create the directory, you can use the Path.mkdir() method.

>>> normal.mkdir()
>>> assert normal.is_dir() # is_dir() returns True if the location exists and is a directory

Recursive Directory Creation

Although that was not yet a recursive creation, we can easily make it recursive with the boolean parents parameter. By default, this parameter is False, meaning that parents are not created, and instead, a FileNotFoundError is raised if there are any missing parent directories. However, if set to True, parent directories are automatically created as required.

>>> nested.mkdir()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/cs/.asdf/installs/python/3.12.4/lib/python3.12/pathlib.py", line 1311, in mkdir
    os.mkdir(self, mode)
FileNotFoundError: [Errno 2] No such file or directory: 'nested/foobar'
>>> nested.mkdir(parents=True)
>>> assert nested.is_dir()

Ignoring Existing Directories

The next handy boolean parameter is exist_ok. By default, this parameter is also False, which means that a FileExistsError is raised if the directory already exists in the file system. We can suppress this error by setting exist_ok=True, in which case the error is only raised if the path exists but is not a directory.

>>> normal.mkdir()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/cs/.asdf/installs/python/3.12.4/lib/python3.12/pathlib.py", line 1311, in mkdir
    os.mkdir(self, mode)
FileExistsError: [Errno 17] File exists: 'normal'
>>> normal.mkdir(exist_ok=True)
>>> some_file = Path("some_file")
>>> assert some_file.is_file() # is_file() returns True if the path exists and is a directory
>>> some_file.mkdir(exist_ok=True)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/cs/.asdf/installs/python/3.12.4/lib/python3.12/pathlib.py", line 1311, in mkdir
    os.mkdir(self, mode)
FileExistsError: [Errno 17] File exists: 'some_file'

File System Permissions and the mode Parameter

The last parameter is the mode parameter, which sets an upper boundary on the file system permissions with which the new directory, but not its parents, if any, are created.

>>> other = Path("other")
>>> assert not other.exists()
>>> other.mkdir(mode=0o777)

The default value of 0o777 (i.e., octal number) represents the maximum possible permissions a file or directory can have. This boundary is then restricted according to your process's umask to compute the actual permissions for the new directory. The computation with bit-level operators is implemented as follows:

>>> mode = 0o777; umask = 0o022; oct(mode & ~umask)
'0o755'

Anyway, this goes beyond the scope of this article, but feel free to reach out if you are curious about this topic. Thank you very much for reading, and see you soon! Also, please subscribe to my mailing list below if you've learned something new and want to receive more articles like this one in your inbox.

Ready to Learn More Web Development?

Join my Mailing List to receive one article per week.


I send one email per week on building performant and resilient Web Applications with Python, JavaScript and PostgreSQL. No spam. Unscubscribe at any time.

Continue Reading?

Here are a few more Articles for you ...


The Built-In all() Function

Learn how to use the built-in all() function in Python for boolean logic, with examples and different implementations.

By Christoph Schiessl on Python

The Built-In chr() and ord() Functions

Discover Python's built-in functions chr() and ord() for handling Unicode characters and converting between integers and characters.

By Christoph Schiessl on Python

How to Format all JSON Files in a Git Repository

Do you struggle with inconsistent JSON formatting across your Git repository? Learn how to reformat everything using Python with this step-by-step guide.

By Christoph Schiessl on DevOps, Git, and Python

Christoph Schiessl

Hi, I'm Christoph Schiessl.

I help you build robust and fast web applications.


I'm available for hire as a freelance web developer, so you can take advantage of the more than a decade of experience I have collected working on many projects across several industries. Most of my clients are building web-based SaaS applications in a B2B context and depend on my expertise in various capacities.

More often than not, my involvement includes hands-on development work using technologies like Python, JavaScript, and PostgreSQL. Furthermore, if you already have an established team, I can support you as a technical product manager with a passion for simplifying complex processes. Lastly, I'm also an avid writer and educator who takes pride in breaking down technical concepts into the simplest possible terms.