14

I am using PyInstaller to create a single-file executable. Is it possible for my script to perform an import such that i) the imported module is imported from the same directory as the exe (i.e. it's not packaged into the exe) and ii) that imported module can import other modules that were packaged into the exe?

The background here is that the imported module contains configuration that the user should be able to modify. This may include creation of custom derived classes and use of enums from the packaged modules.

I haven't found any advice on this, though it's a difficult search because there are so many similar topics that use basically the same keywords.

asked Nov 17, 2017 at 11:53

2 Answers 2

19

The following steps allow a Python module (named external_module here) outside of an executable created by PyInstaller to be imported and for that module to import modules that were bundled into the executable.

  • Add excludes=['external_module'] to the Analysis object used in the PyInstaller spec. This prevents external_module.py being bundled into the executable.
  • Add sys.path.append(os.path.dirname(sys.executable)) where external_module is imported in your application. This allows it to be imported from the directory the executable is in, which is different to the directory that the application will run in (due to being decompressed to a temporary folder). See below for my recommended method of achieving this.
  • Make sure any imports performed by external_module.py are also performed by one of the bundled modules before external_module.py is imported. The interpreter will not resolve the external module's imports against bundled modules, but will use ones that already exist in sys.modules.

In order to set up the paths correctly you can use the following:

if getattr(sys, 'frozen', False):
 app_path = os.path.dirname(sys.executable)
 sys.path.append(app_path)
else:
 app_path = os.path.dirname(os.path.abspath(__file__))

frozen is only available in generated executables, not when running as a script directly. This snippet will add the executable's location to sys.path if required as well as giving you easy access to the executable or script's location for use in code.

As an example of the final bullet point, consider the following.

# bundled_module1.py
import external_module
# bundled_module2.py
# module content
# external_module.py
import bundled_module2

This will fail in external_module.py because bundled_module2 can't be found. However, the following modification to bundled_module1.py will work:

# bundled_module1.py
import bundled_module2
import external_module

This will be fine if there are a limited set of bundled modules that the external one should be able to import. It may get unwieldy for larger sets.

Given that the documentation states that the interpreter will resolve imports against modules bundled into the executable, this feels like a possible bug. Interoperating with modules outside of the executable isn't explicitly called out though.

answered Nov 20, 2017 at 9:15
Sign up to request clarification or add additional context in comments.

Comments

0

Type in Pyinstaller -h. It will give you info about pyinstaller and tell you about --runtime-hook. I presume adding this to the executable should work. There's actually a whole page of documentation for this. Surprised you could not find that.

Anyway,

The docs say put in: pyinstaller --additional-hooks-dir=. myscript.py.

I presume then something like pyinstaller --additional-hooks-dir=C:\pathtofolder myscript.py should work in theory. Yet to test it. Tell us how it goes and what made kinks made it work for you.

Lastly, if you want to be hipster try integrating cython for speed and obfuscation. Fair warning, cython is not as user friendly as pyinstaller appears to be. I have yet to use it successfully.

answered Nov 17, 2017 at 12:06

4 Comments

I did find the hooks documentation, but it looked like it was focused on allowing pyinstaller to find (and bundle) modules imported in odd ways that it would otherwise miss. I'm trying to do the opposite. I'll take another look at that though and see if it's useful.
@Kemp Take a look at stackoverflow.com/questions/15109548/… . It's a nifty workaround. Yet to try it myself, but I may use it in the future. It would mean your files would be exposed though, so probably a last resort.
Thanks for the hints. Progress so far: i) Added excludes=['config'] to the Analysis object used in my PyInstaller spec. This prevents config.py being bundled. ii) Added sys.path.append(os.path.dirname(sys.executable)) where I import the config. This allows it to be imported from the directory the exe is in. Still left to figure out: i) Allowing config.py to import modules that were bundled up. They don't exist in any free-floating form in the temporary execution directory and the interpreter doesn't seem to resolve imports against the bundled modules.
I've added an answer with the solution I ended up using. Any further comments are welcome.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

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.