I have written a class that locates the relative root project directory path from where I'm calling that class.
E.g., I have this structure:
rootProjectDir
|_sub
|__subsub
|___subsubfile.txt
|_another_sub
|__another_sub_file.py
and what I'm doing is when I'm working within another_sub_file.py
, I'm calling my class GetRootPath
to get /home/user/dev/rootProjectDir
, and then I can construct the path to the ./rootProjectDir/sub/subsub/subsubfile.txt
to get that subsubfile.txt
. I didn't find any better solution. I hope there is, but I wrote this class helper for me to achieve this:
import os
class GetRootPath:
def __init__(self):
self.rootFolder = '/rootProjectDir'
def get_root(self):
working_file_dir = os.path.dirname(os.path.abspath(__file__))
i = len(working_file_dir.split('/'))
root = list(working_file_dir.split('/'))
new_root = []
for item in root[1:]:
new_root.append('/' + str(item))
while True:
if self.rootFolder == new_root[-1]:
builded = ''.join(new_root) + '/'
return builded
else:
new_root.pop()
i -= 1
Is there any better way to achieve my goal and also, what could be done better with my class to improve performance and stability of creating relative path to root directory of my project? Or is there a better Pythonic way of navigating your project folder to get files, etc., as you need? My GetRootPath
class looks like a non-Pythonic way of doing this.
I'm working in PyCharm, and my project is created with it, so it's not just folders, I guess.
2 Answers 2
As written, your code assumes it will be running on a filesystem which uses /
as a directory separator, such as Unix, Linux, and MacOS. It will never work when run on a filesystem with \
as the separator, such as Windows. While you could try to fix this using os.sep
, there is an easier way...
pathlib.Path
is an object-oriented interface to the filesystem. Using it frees you from needing to worry about which separator needs to be used, and from issues with the file system root which necessitated the unexplained root[1:]
in your code, which "seems to" ignore the first component of the filesystem path.
Once you've converted the filename to a Path
object, Path.parts
will split it into the individual components. Count backwards until you find the required rootProjectDir
, and then use Path.parents
to retrieve full path to that directory. Eg)
from pathlib import Path
def get_root_path() -> Path:
path = Path(__file__).absolute()
idx = path.parts[::-1].index("rootProjectDir")
return path.parents[idx - 1]
I've replaced the class containing a single method with a simple function. Still, the above still feels wrong. The project root directory doesn't ever change. Moreover, the file itself likely knows its position in the project structure, so it doesn't really need the name of the project directory.
Your project is laid out like ...
rootProjectDir
|_sub
| |_subsub
| |_subsubfile.txt
|_another_sub
|_another_sub_file.py
Is another_sub
a Python package? If so, when inside the another_sub_file.py
module, __name__
will be another_sub.another_sub_file
. Since the module name has two components, you can immediately determine the project root directory is the grandparent of this __file__
.
from pathlib import Path
ROOT_PATH = Path(__file__).absolute().parents[__name__.count('.')]
Now the project root is computed once when the file is imported, not repeatedly every time you want to access a different file.
DRY
This expression appears on 2 consecutive lines:
working_file_dir.split('/')
You could set it to a variable so that split
is only called once.
However, as noted in the comments on the question, the i
variable
is not needed, which means these 2 lines can be deleted:
i = len(working_file_dir.split('/'))
i -= 1
Simpler
These lines:
builded = ''.join(new_root) + '/'
return builded
can be combined into a single line:
return ''.join(new_root) + '/'
This eliminates the poorly-named builded
variable.
Also, there is no need for the else
line after the return
. here is
the simplified while
loop:
while True:
if self.rootFolder == new_root[-1]:
return ''.join(new_root) + '/'
new_root.pop()
The code might be simpler without the class since there is really only one function.
Documentation
The PEP 8 style guide recommends adding docstrings for classes and functions.
Naming
The PEP 8 style guide recommends snake_case for function and variable names.
For example, rootFolder
would be root_folder
.
Array variables are commonly pluralized; root
would be better as roots
.
i
? \$\endgroup\$GetRootPath
is supposed to solve? \$\endgroup\$