130

I have an application that resides in a single .py file. I've been able to get pyInstaller to bundle it successfully into an EXE for Windows. The problem is, the application requires a .cfg file that always sits directly beside the application in the same directory.

Normally, I build the path using the following code:

import os
config_name = 'myapp.cfg'
config_path = os.path.join(sys.path[0], config_name)

However, it seems the sys.path is blank when its called from an EXE generated by pyInstaller. This same behaviour occurs when you run the python interactive command line and try to fetch sys.path[0].

Is there a more concrete way of getting the path of the currently running application so that I can find files that are relative to it?

2
  • possible duplicate of how can i get the executable's current directory in py2exe? -- This current question is older but the other question has more answers and is more documented. Commented Nov 12, 2013 at 15:07
  • 12
    These have a lot in common but py2exe != pyinstaller Commented Oct 26, 2015 at 0:18

10 Answers 10

193

I found a solution. You need to check if the application is running as a script or as a frozen exe:

import os
import sys

config_name = 'myapp.cfg'

# determine if application is a script file or frozen exe
if getattr(sys, 'frozen', False):
    application_path = os.path.dirname(sys.executable)
elif __file__:
    application_path = os.path.dirname(__file__)

config_path = os.path.join(application_path, config_name)
Sign up to request clarification or add additional context in comments.

5 Comments

FYI: For newer PyInstaller releases (3.0+), see @normanius answer.
Shouldn't there be only else: instead of elif __file__:?
For users that like to use pathlib: bundle_dir = Path(sys.executable).parent also, you might want to check hasattr(sys, '_MEIPASS'). (should be True)
This is the only solution that actually works, that is clean, and that it doesn't utilize that sys._MEIPASS stupidity.
Also see @Max Tet comment on normanius answer.
111

According to the documentation of PyInstaller, the suggested method of recovering application path is as follows:

#!/usr/bin/python3
import sys, os
if getattr(sys, 'frozen', False):
    # If the application is run as a bundle, the PyInstaller bootloader
    # extends the sys module by a flag frozen=True and sets the app 
    # path into variable _MEIPASS'.
    application_path = sys._MEIPASS
else:
    application_path = os.path.dirname(os.path.abspath(__file__))

Tested for PyInstaller v3.2, but this certainly has been working for earlier versions as well.

Soviut's solution does not work, at least not in general for recent versions of pyInstaller (note that the OP is many years old). For instance, on MacOS, when bundling an application into a one-file-bundle, sys.executable points only to the location of the embedded archive, which is not the location where the application actually runs after the pyInstaller bootloader has created a temporary application environment. Only sys._MEIPASS correctly points to that location. Refer to this doc-page for further information on how PyInstaller works.

6 Comments

Can confirm that this works on Windows 10 and Windows 7 with PyInstaller 3.2 and Python 3.4 -- other answers no longer work.
just adding that according to the docs this works for both one-file and one-folder distributions
Using sys._MEIPASS does not work for one-file executables. From the docs: For a one-folder bundle, this is the path to that folder, wherever the user may have put it. For a one-file bundle, this is the path to the _MEIxxxxxx temporary folder created by the bootloader . Use sys.executable for one-file executables.
for a --onefile executable, the path to the application is given by application_path = os.path.dirname(sys.executable)
Just tested with pyinstaller version 5.0 and it worked like a charm. This is the only answer that worked for me. Also, using __file__ to get absolute path was the missing point for me.
|
13

I shortened the code a bit.

import os, sys

if getattr(sys, 'frozen', False):
    application_path = os.path.dirname(sys.executable)
    os.chdir(application_path)

logging.debug('CWD: ' + os.getcwd())

But, sys._MEIPASS pointed to a wrong directory. I think it also needs sys._MEIPASS + \app_name

Comments

7

I'm surprised nobody has mentioned that getattr() has a built-in default argument which will be returned if the attribute doesn't exist. This can also be made a bit more readable with pathlib. This code works whether or not the code is bundled with PyInstaller.

from pathlib import Path
bundle_dir = Path(getattr(sys, '_MEIPASS', Path.cwd()))
config_path = bundle_dir / 'myapp.cfg'

2 Comments

Shouldn't that last line be config_path = bundle_dir + '/myapp.cfg'? Otherwise good answer
@EdwardSpencer, nope, pathlib.Path objects let you use the / operator to build a path.
6

__file__ works from command line with python executable. It also gives the script file name without actual path in frozen mode. However it gives error in interactive mode.

The following will work for all three modes:

import sys,os

config_name = 'myapp.cfg'

if getattr(sys, 'frozen', False):
    application_path = os.path.dirname(sys.executable)
    running_mode = 'Frozen/executable'
else:
    try:
        app_full_path = os.path.realpath(__file__)
        application_path = os.path.dirname(app_full_path)
        running_mode = "Non-interactive (e.g. 'python myapp.py')"
    except NameError:
        application_path = os.getcwd()
        running_mode = 'Interactive'

config_full_path = os.path.join(application_path, config_name)

print('Running mode:', running_mode)
print('  Appliction path  :', application_path)
print('  Config full path :', config_full_path)

Output in three different modes:

Running mode: Interactive
  Appliction path  : C:\Projects\MyAppDir
  Config full path : C:\Projects\MyAppDir\myapp.cfg

C:\Projects\MyAppDir>myapp.exe
Running mode: Frozen/executable
  Appliction path  : C:\Program Files\myapp
  Config full path : C:\Program Files\myapp\myapp.cfg

C:\Projects\MyAppDir>python myapp.py
Running mode: Non-interactive (e.g. 'python myapp.py')
  Appliction path  : C:\Projects\MyAppDir
  Config full path : C:\Projects\MyAppDir\myapp.cfg

C:\Projects\MyAppDir>

2 Comments

I have tried all of the above with an executable created with pyinstaller and this is the only answer that worked. Thanx
I also tried all other solutions and this is the only answer that worked for me as well. Thank you soo much!!!
3
os.path.dirname(sys.argv[0])

That works for me.

1 Comment

This only works if you use the absolute path to the executable when calling it. Using just the exe's name and having found via the PATH returns an empty path.
2

Many answers here but I found this solution works in most situations:

import os
import sys
import os.path as op
try:
    this_file = __file__
except NameError:
    this_file = sys.argv[0]
this_file = op.abspath(this_file)
if getattr(sys, 'frozen', False):
    application_path = getattr(sys, '_MEIPASS', op.dirname(sys.executable))
else:
    application_path = op.dirname(this_file)

1 Comment

For me this results in the runtime path, in AppData\Local\Temp_MEI... the question was about the folder where the EXE file resides.
0

There are two questions being talked about on this forum that look interchangeble but have very different answers.

The first question...

...which is what the OP seems to want but doesn't quite specify, is about finding external files that are not part of the application, are intended for the user to see and interact with and possibly modify or provide themselves. In onefile mode, the external files will still be visble. In PyInstaller >=v6 onedir mode, the files will be outside of the _internal directory. And for macOS .app bundles, this won't make any sense at all.

For this, the closest answer would be the following (but please read on before splodge+pasting):

if getattr(sys, "frozen", False):
    app_location = os.path.dirname(sys.executable)
else:
    app_location = os.path.dirname(sys.modules["__main__"].__file__)
    # Just os.path.dirname(__file__) is OK if this is the top level script

For the curious, the above is mostly equivalent to using os.path.dirname(sys.argv[0]) except:

  • If the executable's location is added to PATH then invoked by name then sys.argv[0] would just the exectuable's name rather than its location.
  • If the executable is invoked via a symlink, sys.argv[0] will point to the symlink but sys.executable will point to the resolved file.

A word of caution though: It is normal for security concious users to install applications into readonly locations which is obviously incompatible with the idea of having a writable config file next to the application. Additionally, what exactly this location means get ambiguous in say a macOS .app bundle or an unfrozen Python application invoked via python -m package_name or a console script entry point. To that end, I encourage instead putting configuration in the user's home directory, even though it breaks the nice asthetics of being self contained (possibly with an opt-in portable mode if you prefer).

The second question...

... which wasn't strictly asked but is an understandable interpretation of application path and is what most people have answered, is about locating application resources shipped inside the application. These are files that user shouldn't be touching and, in onefile mode, won't even be able to see. I want to address this one since I see these invalid (to both questions) answers being proliferated all across the internet. Every other answer on here addressing that question is broken (with the exception of sys._MEIPASS which is merely unnecessary).

The correct method is to use __file__ in exactly the same way as you would if you weren't using PyInstaller. You shouldn't need any if getattr(sys, "frozen", False) forks anywhere.

# These will work regardless of whether or not you're using PyInstaller
resource = pathlib.Path(__file__).resolve().with_name("resource.txt")
# Or
resource = os.path.join(os.path.dirname(os.path.realpath(__file__)), "resource.txt")

If that ends up looking in the wrong place then that means that the dest part of your --add-data options needs adjusting so that your frozen application layout is the same as your normal application layout. Always adjust your frozen application so that it's consistent with your unfrozen layout and your Python code – never do it backwards and adjust your Python code to compensate for an inconsistent packaging layout.

Why you should never use the other answers (even if you're not using PyInstaller):

  • Anything involving sys.path[0] is just a wild heuristic that sometimes happens to work because Python usually prepends your script's location to sys.path. Just running python -I your-code.py is enough to break that assumption. This will also break if you make your code into a pip installable package so that it moves to a different entry in sys.path.
  • Using sys.executable will break in onefile mode or with macOS .app bundles since the application contents are not next to the executable. It also breaks in onedir mode in PyInstaller v6 since the application resources are moved into a subdirectory unless you specify --contents-directory=. (an option that only exists because people made such a fuss about refusing to fix their invalid os.path.dirname(sys.executable) based resource locating).
  • Using sys.argv[0] has all the issues of sys.executable but will aditionally break if the application is launched via a symlink.
  • Using os.cwd() is equivalent to and no less broken than just using a relative path.. It assumes that your code is in your current working directory. python test.py will give the illusion of working but python somewhere/else/test.py will break.
  • Using os.chdir() affects the global process state so if two libraries try to do this, the one that loads second will break the one that loads first. It'll also break legitimate usages of relative paths such as those passed in as command line arguments to a console application.

Comments

-1

My case is using a service that runs an executable .exe build with pyinstaller. I use os.path.dirname(**os.path.realpath(sys.executable)**)

import os
import sys
# determine if application is a script file or frozen exe
if getattr(sys, 'frozen', False):
    application_path = os.path.dirname(os.path.realpath(sys.executable))
elif __file__:
    application_path = os.path.dirname(__file__)

Comments

-1

Here's how I handle my pyinstaller relative paths if I have a folder next to the .exe I want to use:

import os
import pathlib

os.environ['base_path'] = os.path.dirname(sys.argv[0])
files_to_process = glob.glob( str(pathlib.Path(os.environ.get('base_path')) / 'to_process/*.pdf') )

This works whether you're executing as a script or exe.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.