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.