You can keep track of paths in local variables and use them as needed. pathlib
offers a convenient wrapper class for this.
>>> from pathlib import Path
>>> project_path = Path("project_name")
>>> project_path
PosixPath('project_name')
>>> str(project_path)
'project_name'
You can list
>>> print(list(project_path.iterdir()))
[PosixPath('project_name/app1'), PosixPath('project_name/app2')]
And make subpaths
>>> app1_path = project_path.joinpath("app1")
>>> app1_path
PosixPath('project_name/app1')
>>> str(app1_path)
'project_name/app1'
You can still run commands with os.system
(notice the semicolon separating two commands)
>>> import os
>>> rc = os.system(f"cd {app1_path};ls")
foo some_file
or use the subprocess module which allows you to specifiy current working directory
>>> import subprocess as subp
>>> rc = subp.run("ls", shell=True, cwd=app1_path)
foo some_file
Code that unilaterally changes path can be hard to read. And its easy to get lost and do work in the wrong directory.