Python is a great language to quickly create applications and tools on the fly. In the event that you need to create something a little more involved, there are variety of packages out there to support things from GUI development, Machine learning, Computer Vision, and so much more. But what happens when there comes a time that you need to share your tool with a third party for testing? Or better yet – when you need to deploy the application to another machine, and you are unable to install what you need on the new machine? What if your end user has no idea how to install the proper interpreter and packages? During times like these it is best to roll up your project into a single file executable to distribute everything your end user would ever need for it.
In this article we will be looking at the following Python standalone executable generators:
- PyInstaller, version 6.10.0
- Nuitka, version 2.4.8
- CX_Freeze, version 7.2.0
Each one of the standalone executable generators will be examined as-is when you initially install them on your build machine. Python version 3.12.5 will be used with each.
To test the capabilities of these standalone executable generators, the following Python script will be used. This script consists of using the Tkinter package to display a text entry box, a button to submit the text string entered by the user, and a list box to echo back the string entered by the user.
Example Program
1 2 3 4 5 6 7 8 9 10 11 12 |
from main_window_frame import MainWindowFrame def main(): main_window = MainWindowFrame() main_window.construct_label_frames() while(main_window.i_exist()): main_window.update() return if __name__ == '__main__': main() |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
import tkinter import datetime from datetime import timezone class MainWindowFrame(): def __init__(self) -> None: self.i_exist_var = True self.main_window = tkinter.Tk() self.main_window.protocol('WM_DELETE_WINDOW', self._on_close_main_window) self.main_window.title('Test Entry Application') self.main_window.grid_columnconfigure(0, minsize=500) self.main_window.resizable(1,1) def construct_label_frames(self): self.construct_data_disp_frame() self.construct_data_entry_frame() # Grid all Label Frames self.data_entry_label_frame.grid(row=0, column=0, columnspan=10) self.data_disp_label_frame.grid(row=1, column=0, columnspan=10) def construct_data_entry_frame(self): self.data_entry_label_frame = tkinter.LabelFrame(self.main_window, text='Data Entry Frame') data_entry_entry = tkinter.Entry(self.data_entry_label_frame, width=50) self.data_entry_widget_var = tkinter.StringVar() self.data_entry_widget_var.set('') data_entry_entry['textvariable'] = self.data_entry_widget_var data_entry_button = tkinter.Button(self.data_entry_label_frame, text='Submit Data', command=self._on_enter_data) # Grid widgets in Label Frame data_entry_entry.grid(row=0, column=0) data_entry_button.grid(row=0, column=1) def construct_data_disp_frame(self): self.data_disp_label_frame = tkinter.LabelFrame(self.main_window, text='Data Display Frame') self.data_disp_listbox = tkinter.Listbox(self.data_disp_label_frame, height=20, width=100) # Grid Widget in Label Frame self.data_disp_listbox.grid(row=0, column=0) def i_exist(self): return self.i_exist_var def update(self): if self.i_exist == False: return self.main_window.update_idletasks() self.main_window.update() def _on_enter_data(self): # Get data from entry data_string = f'[{datetime.datetime.now(timezone.utc)}] {self.data_entry_widget_var.get()}' # Update listbox with entry data self.data_disp_listbox.insert(tkinter.END, data_string) # Clear data from Entry self.data_entry_widget_var.set('') def _on_close_main_window(self): self.i_exist_var = False self.main_window.destroy() |
It should be noted that these tests will only be performed on a Windows machine, but all three of the Python standalone executable generators discussed in this article will function on Windows, Linux, and MacOS.
PyInstaller
Possibly the most well-known standalone executable generator out of the three, PyInstaller allows you to quickly create a standalone executable while providing support for most of all the major Python packages, such as numpy, matplotlib, PyQT, and more. With the ability to run on Windows, Linux, and MacOS, PyInstaller is a great tool to use if you are wanting something quick and easy.
For a comprehensive list of everything PyInstaller can do, check out the official documentation.
Pros:
- Able to generate executables for Windows, Linux, and MacOS.
- Capable of generating a one-folder (standalone directory) executable that contains all compiled and relevant files.
- Capable of generating a one-file executable, a single binary that will extract itself on the target before it runs.
- Note: It is advised to get a “standalone” version of your program functioning first before generating a one-file executable.
- Fast compilation times.
- Auto includes any modules that are imported in the script. Options to directly define modules and packages to include, are included.
- Bundles the proper Python interpreter to run the executable.
- Re-compilation is made easier if you have an existing “spec” file.
Cons:
- No cross compilation. You will have to run and build on the OS you intended to deploy to.
- Compiles Python scripts to .pyc files. These can possibly be decompiled, revealing the source code.
- Lack of OS specific configurations when compared to other standalone executable generators.
Build Examples
The following command can be used to create a one-folder executable for the Python program defined in the Example Program section:
- cmd> python -m PyInstaller main.py
This command is used to generate a one-folder executable name “main” (based on the name of the python module supplied at build-time) which is placed inside of the default distribution folder location of “dist”. Note that the names and locations of the files/folders can be modified if desired (see the official Options documentation page for more details).
The generated one-file folder can then be compressed to a zip folder and transferred to another machine. Users will then have to unzip the folder and launch the executable found within to use the application.
Now let’s use a slightly more complex build command:
- cmd> python -m PyInstaller -F -w main.py
Here the “-F” argument is used to specify that a one-file executable is to be generated, and “-w” is used to indicate that a terminal window is not to be shown whenever the application starts (note that this is specific to Windows platforms). A one-file executable name “main” will be generated and placed in the default folder location of “dist”. This single application file can then be transferred to another machine and users will only have to execute it to use the application.
Nuitka
Possibly one of the strongest Python standalone executable generators on the list, Nuitka is capable of converting your Python scripts to C level programs and compiling them from there. This will effectively snuff out any possibility of bad actors decompiling your source code.
When it comes to configurations for how the standalone executable is built and bundled, Nuitka provides a variety of options, such as defining what compiler to use, what directories can be used when unpack a one-file executable during runtime, defining OS specific properties, and more.
If you are so inclined to get even more out of the Nuitka, they do offer a paid commercial version which includes more code protection features for your projects.
For a comprehensive list of everything Nuitka can do, check out the official documentation.
Pros:
- Translates all Python scripts to C level programs and then compiles from there. This effectively hides source code.
- Capable of aggregating all complied files and required resources into a standalone (user defined) directory.
- Capable of generating a one-file executable, a single binary that will extract itself on the target before it runs.
- Note: It is advised to get a “standalone” version of your program functioning first before generating a one-file executable.
- Multiple options for modifying build and OS specific configuration.
- Ability to specify where to unpack one-file executables on machines.
- Bundles the proper Python interpreter to run the executable.
Cons:
- No cross compilation. You will have to run and build on the OS you intended to deploy to.
- Compilation can take a while to complete, regardless of the compiler chosen. There are configurations to help mitigate this, but results will vary.
- Abundance of configuration options can lead to missing modules/packages, your executable failing to run, or missing OS details if not careful.
- One-file configuration can have issues with locating files relative to the location of the executable.
Build Examples
The following command can be used to create a one-folder executable for the Python program defined in the Example Program section:
- cmd> python -m nuitka --follow-imports --plugin-enable=tk-inter --standalone main.py
This command will generate a one-folder executable named “main” (based on the name of the python module supplied at build-time) which is placed inside of the default distribution folder location of “main.dist”. The “-follow imports” argument will allow Nuitka to descend into all imported modules, in relation to the module supplied at build-time, and include the modules found in the files to compile. The “—plugin-enable=tk-inter” is needed in order to properly include modules related to TKinter.
The generated one-file folder can then be compressed to a zip folder and transferred to another machine. Users will then have to unzip the folder and launch the executable found within to use the application.
CX Freeze
CX Freeze provides easy to use configurations, similarly to PyInstaller, allowing you to quicky generate a standalone executable on the fly while providing a variety or other different build and bundle options. Along with being able to generate a standalone executable, CX Freeze also provides a method of generating a simple installer for Windows or MacOs, with the ability to define certain behaviors of the installer.
For a comprehensive list of everything CX Freeze can do, check out the official documentation.
Pros:
- Able to generate executables for Windows, Linux, and MacOS.
- Simple and straight forward commands for generating standalone or one-file executables.
- Fast compilation times.
- Can create and define a setup file, full of configurations, and installation configurations.
- Auto includes any modules that are imported in the script. Options to directly define modules and packages to include, are included.
- Bundles the proper Python interpreter to run the executable.
Cons:
- No cross compilation. You will have to build on the respective OS you intend to deploy to.
- Cannot create a single file executable.
- Compiles Python scripts to .pyc files. These can possibly be decompiled, revealing the source code.
Build Examples
For this example, a Python script will be created for specifying the build instructions for the one-folder executable and the Windows installer used to install the application on another machine.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
from cx_Freeze import Executable, setup bdist_msi_options = { "target_name": "APP_INSTALLER", # Name of the installer executable } executables = [ Executable( "main.py", # Name of the main entry module copyright="Copyright (C) 2024 Sparx", # Copyright statement in Windows base="gui", # Application type ) ] setup( name="Main App", # Product name in Windows version="0.1", # File version in Windows description="Sample app to test cx_Freeze functionality.", # File description in Windows executables=executables, options={ "build_exe": {}, "bdist_msi": bdist_msi_options, }, ) |
From here the following command can be used to run the script:
- cmd> python -m setup.py bdist_msi
This will compile the project files and create an installer executable name “APP_INSTALLER” that can be found in the default distribution folder location of “dist”. This installer executable can then be transferred to other machines and ran in order to install the application. The “main” application can then be launched to use the application.
There are a variety of configurations that can be applied to the build process of the application and for the installer executable. More information can be found in the official CX Freeze documentation.
Conclusion
Depending on what you need, when you need it, and the restrictions for the environment you are planning to deploy your application/tool in, one of the three Python standalone executable generators covered in this article will have you covered. The great thing about each one is the amount of documentation provided for you to get you started, with the only limit being how much you want to invest in getting familiar with any one of them.