I'm currently still in the early stages of learning Python and Jupyter notebooks, and I wanted to ask if the following code for returning absolute paths relative to the directory of a script (.py) or Jupyter notebook (.ipynb) file could be improved, particularly on the test which checks whether the absolute_path() function is called in a .py or .ipynb file. ruff tells me not to use bare except, but I don't know how to deal with the warning.
# $HOME/
# └─ project/
# ├─ data/
# └─ src/
# └─ project/
# ├─ project.ipynb
# └─ project.py
# A script with path
# $HOME/project/src/project/project.py
# or a cell inside a Jupyter notebook with path
# $HOME/project/src/project/project.ipynb
from pathlib import Path
import ipynbname
from IPython import get_ipython
def absolute_path(relative_path: str | None = None) -> Path:
try:
get_ipython()
file_path = Path(ipynbname.path())
except:
file_path = Path(__file__).resolve()
file_dir_path = file_path.parent
path = Path(
f"{file_dir_path}"
if relative_path is None
else f"{file_dir_path}/{relative_path}"
).resolve()
return path
root_dir_path = absolute_path("../..")
data_dir_path = absolute_path("../../data")
src_dir_path = absolute_path("..")
file_dir_path = absolute_path()
print(f"{root_dir_path = }")
print(f"{data_dir_path = }")
print(f"{src_dir_path = }")
print(f"{file_dir_path = }")
4 Answers 4
One thing the other answers haven't addressed is this:
file_dir_path = file_path.parent
path = Path(
f"{file_dir_path}"
if relative_path is None
else f"{file_dir_path}/{relative_path}"
).resolve()
return path
Repeatedly making Paths into strings and back again isn't very Pythonic. Instead, consider using something like:
path = file_path.parent
if relative_path is not None:
path /= relative_path
return path.resolve()
-- or indeed, remove the relative_path altogether and use the / operator outside of the call to absolute_path, like Kate suggests.
-
\$\begingroup\$ I think
if relative_path: ...suffices. \$\endgroup\$Gribouillis– Gribouillis2025年12月07日 17:52:44 +00:00Commented Dec 7 at 17:52
I've read the code of the ipynbname module and you could replace the bare except clause by
except FileNotFoundError:
This is publicly documented in the docstring of ipynbname.path().
Also you could remove the call to get_ipython() as you don't do anything with its return value, so I don't think it serves any purpose. (actually ipynbname.path() internally calls get_ipython() but it does use the return value).
try/except
rufftells me not to use bareexcept, but I don't know how to deal with the warning
You should investigate what type of error you expect to get from the code following the
try statement. If the documentation for get_ipython mentions different types
of common error situations, or if you have encountered specific errors when using it,
then you would want to trap those error types. The reason for the recommendation from ruff
is to avoid trapping all errors. Doing so may make the code difficult to debug.
Documentation
The PEP 8 style guide recommends adding docstrings for functions and at the top of the code to summarize its purpose.
"""
Return absolute paths relative to the directory of a script (.py)
or Jupyter notebook (.ipynb) file.
Also state why this code is useful.
"""
First of all, congrats for using ruff. If you are using code versioning (and you should), then consider using ruff as a pre-commit hook (reference).
Now there is inconsistency in what the function absolute_path suggests and what it actually does. The name absolute_path implies that it will indeed return an absolute path. But it takes a relative_path parameter. So the end result is something else. Apples and oranges. Just return the absolute_path. I would be even more precise and name that function get_application_path or something along these lines, so there is no ambiguity.
In plain Python, I would use something like this to get the directory of the running script:
current_dir = os.path.dirname(os.path.realpath(__file__))
But I am not familiar with IPython and there might be a better way. Leaning heavily on a specific Python build makes the script potentially less portable though.
Better paths
Since you are using pathlib, you can embrace the slash notation (pathlib reference) eg:
from pathlib import Path
data_dir_path = absolute_path() / "." / "." / "data"
Storage
I suspect you are looking to store data in your app, then the importlib.resources lib should interest you. Especially if you are looking to embed resources (icons, files etc) used by your app.
-
1\$\begingroup\$ you write
data_dir_path = absolute_path() / "." / "." / "data"as a replacement fordata_dir_path = absolute_path("../../data")- this is wrong and also not the best way to utilizepathlib. Better would beabsolute_path().parents[1] / "data"orabsolute_path().parent.parent / "data", sincepathlibnatively includes ways to get the parent directories. \$\endgroup\$fyrepenguin– fyrepenguin2025年12月07日 17:09:19 +00:00Commented Dec 7 at 17:09