How to get full path of resource file from within python script?

Viewed 3448

Ok, so i made a python script places inside a package. The tree looks something like this:

├── foo
│   ├── __init__.py
│   ├── funcs
│   │   ├── __init__.py
│   │   └── stuff.py
│   ├── resources
│   │   └── haarcascade_frontalface_default.xml
│   └── scripts
│       ├── __init__.py
│       └── script.py
└── setup.py

So inside the script file, im using openCV's cv2 to detect faces, and for that the cv2.CascadeClassifier requires the path of the XML file located under /resources. Now because this is a script, i need to be able to run it from anywhere, so a relative path to the resource file sadly doesn't do the trick. How can I get the absolute path to the xml file from within script.py? You can assume that the script and the xml file is located relative to each other respectively, just like the example above. Thanks :))

PS: Bonus if the solution works with eggs as well. Much appreciated

3 Answers

Currently the best way to do this is importlib.resources. Since Python 3.7 it is available in the standard library. For earlier versions, there is a backport called importlib_resources.

Follow the documentation.

In your case this should more or less look like this :

import importlib.resources
xml_path = importlib.resources.path('foo.resources', 'haarcascade_frontalface_default.xml')

There are many advantages to this, most importantly it is standard and it will work wherever the package is installed even if it's in a zip file.

In your case, you might have to add an __init__.py file to your resources directory.

Using the os module works, but if you have access to a python version >= 3.4, then pathlib is an alternative that handles itself a little easier and performs better across platforms:

from pathlib import Path

# when using pathlib.Path, slashes get automatically transformed into the 
# correct path-division character, depending on the platform
RESOURCES_PATH = Path(__file__).parent.parent / "resources"

face_cascade = cv2.CascadeClassifier()
face_cascade.load(RESOURCES_PATH / "haarcascade_frontalface_default.xml")

If you find yourself defining lots of these kinds of constants, consider putting all of them in a file like foo/util.py so that they are easily reusable within your project and don't need to be re-declared or imported from a script.


An even better option in python versions >=3.7 is using importlib.resources.path, which resolves resources automatically from the package root, so you don't need to find it by hand by walking up from __file__:

import importlib

face_cascade = cv2.CascadeClassifier()
with importlib.resources.path("foo.resources", "haarcascade_frontalface_default.xml") as haar_resource:
    # haar_resource is a pathlib.Path object here as well, so plugging it is simple
    face_cascade.load(haar_resource)

This is a lot more elegant and should be the preferred solution given it's available.

I'm not sure I understand the question correctly, but maybe os.path will help? Something like:

>>> import os
>>> os.path.abspath("directory/somefile.txt")
'C:/somedirectory/directory/directory/somefile.txt'
Related