Recently I’m using “pyinstaller” and here are some notes from my recent experience.
How to include Python files loaded dynamically?
In my code I use “importlib.import_module(…)” to load module dynamically. After packaged with “pyinstaller”. Running the program gets error that the target module cannot be found. The reason is, “pyinstaller” will parse the Python code and only include the modules explicitly imported.
To fix the issue, I can either:
Add explicit “import” statement in Python code(it’s OK not to use the explicitly imported module since the dynamically importing will work anyway);
Add the “–hidden-import” argument(s) when executing “pyinstaller”, such as:
1 | pyinstaller --hidden-import 'tap.commands' --hidden-import 'tap.commands.id' tap.py |
I choose option 1 since adding the explicit “import” statements is much clearer than updating the “pyinstaller” command.
How to package data(resource) files?
In my code, some data files locates at a project package which are loaded on runtime. The code works file until it’s running within the executable file compiled by “pyinstaller”. The reason is that such data files are not included when “pyinstaller” does the packaging. To fix this issue, 2 steps are required:
- Add the “–add-data” argument when executing “pyinstaller”, such as:
1 | pyinstaller --add-data "tap/commands/*.template:tap/commands" tap.py |
- Update project code by using “pkgutil” to read the data file instead of reading the target file directly:
1 | import pkgutil |
Summary
Packaging everything into one executable file makes running a Python program much easier, but it breaks some fundamental assumptions how a Python program runs, which brings some extra cost to make it work.