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.

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 my more than a decade of experience 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 an avid writer and educator who takes pride in breaking technical concepts down into the simplest possible terms.

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

Function Definition with Simple Parameters

Learn about functions with simple parameters in Python, including how the called can decide to use positional or keyword notation.

By Christoph Schiessl on Python

Function Definition with Default Parameters

Learn about Python functions with default parameters. Understand how default parameters work and some essential restrictions and evaluation rules.

By Christoph Schiessl on Python

Web App Reverse Checklist

Ready to Build Your Next Web App?

Get my Web App Reverse Checklist first ...


Software Engineering is often driven by fashion, but swimming with the current is rarely the best choice. In addition to knowing what to do, it's equally important to know what not to do. And this is precisely what my free Web App Reverse Checklist will help you with.

Subscribe below to get your free copy of my Reverse Checklist delivered to your inbox. Afterward, you can expect one weekly email on building resilient Web Applications using Python, JavaScript, and PostgreSQL.

By the way, it goes without saying that I'm not sharing your email address with anyone, and you're free to unsubscribe at any time. No spam. No commitments. No questions asked.