Reputation: 58768
I've got this function:
def relative_path(*paths):
return os.path.join(os.path.dirname(__file__), *paths)
How would I change it to return the path relative to the caller?
For example, if I called relative_path('index.html')
from another script, is it possible to get the path relative the script from where it was called implicitly or would I need to modify relative_path
to pass __file__
across as well like this?
def relative_path(__file__, *paths):
return os.path.join(os.path.dirname(__file__), *paths)
Upvotes: 4
Views: 2585
Reputation: 2022
Note that tracing stack would be a possibility here, but it could cause some serious trouble ( like confusing 'garbage collector', or may not even work in eggs )
I believe the cleanest way would be to pass the caller to the rel_path function.
However, as you may know, there is usually an ugly way of doing things in python. You can do for example something like this:
consider following two scripts:
# relpath.py
import os
def rel_path(path):
if os.path.isfile(__name__):
return os.path.relpath(path, start=__name__)
print("Warning: %s is not a file: returning path relative to the current working dir" % __name__, file=sys.stderr)
return os.path.relpath(path)
# caller.py
import importlib.util
spec = importlib.util.spec_from_file_location(name=__file__, location="/workspace/relpath.py")
rel = importlib.util.module_from_spec(spec)
spec.loader.exec_module(rel)
print(rel.rel_path("/tmp"))
What we did here: when loading the module using importlib.util, we passed the name=__file__
, which gave our module the name consisting of the caller script path. Hence we needn't pass it as an argument to the relpath.py.
Note that this is not clean solution and might not be readable for future developers reading your code. I just wanted to demonstrate the possibilities of python.
Upvotes: 0
Reputation: 758
adapting the solution in Get __name__ of calling function's module in Python
file1.py
import os
import inspect
def relative_path(*paths):
return os.path.join(os.path.dirname(__file__), *paths)
def relative_to_caller(*paths):
frm = inspect.stack()[1]
mod = inspect.getmodule(frm[0])
return os.path.join(os.path.dirname(mod.__file__), *paths)
if __name__ == '__main__':
print(relative_path('index.html'))
sub/sub_file.py
import sys
sys.path.append(r'/Users/xx/PythonScripts/!scratch')
import file1
if __name__ == '__main__':
print(file1.relative_path('index.html'))
print(file1.relative_to_caller('index.html'))
Running sub_file.py gives the following output:
/Users/xx/PythonScripts/!scratch/index.html
/Users/xx/PythonScripts/!scratch/sub/index.html
There are some caveats in the comments to the question in the link above...
Upvotes: 3