PyInstaller – How to Turn Your Python Code into an Exe on Windows

You have just created an awesome new application. Maybe it’s a game or maybe it’s an image viewer. Whatever your application is, you want to share it with your friend or a family member. However, you know they won’t know how to install Python or any of the dependencies. What do you do? You need something that will transform your code into an executable!

Python has many different tools you can use to convert your Python code into a Windows executable. Here are a few different tools you can use:

  • PyInstaller
  • py2exe
  • cx_freeze
  • Nuitka
  • Briefcase

These various tools can all be used to create executables for Windows. They work slightly differently, but the end result is that you will have an executable and perhaps some other files that you need to distribute too.

PyInstaller and Briefcase can be used to create Windows and MacOS executables. Nuitka is a little different in that it turns your Python code into C code before converting it into an executable. What this means is that the result ends up much smaller than PyInstaller’s executable.

However, for this article, you will focus on PyInstaller. It is one of the most popular packages for this purpose and has a lot of support. PyInstaller also has good documentation and there are many tutorials available for it.

In this article, you will learn about:

  • Installing PyInstaller
  • Creating an Executable for a Command-Line Application
  • Creating an Executable for a GUI

Let’s transform some code into a Windows executable!

Installing PyInstaller

To get started, you will need to install PyInstaller. Fortunately, PyInstaller is a Python package that can be easily installed using pip:

python -m pip install pyinstaller

This command will install PyInstaller and any dependencies that it needs on your machine. You should now be ready to create an executable with PyInstaller!

Creating an Executable for a Command-Line Application

The next step is to pick some code that you want to turn into an executable. You can use the PySearch utility from chapter 32 of my book, Python 101: 2nd Edition, and turn it into a binary. Here is the code:

# pysearch.py

import argparse
import pathlib

def search_folder(path, extension, file_size=None):
    """
    Search folder for files
    """
    folder = pathlib.Path(path)
    files = list(folder.rglob(f'*.{extension}'))

    if not files:
        print(f'No files found with {extension=}')
        return

    if file_size is not None:
        files = [f for f in files
                 if f.stat().st_size > file_size]

    print(f'{len(files)} *.{extension} files found:')
    for file_path in files:
        print(file_path)


def main():
    parser = argparse.ArgumentParser(
        'PySearch',
        description='PySearch - The Python Powered File Searcher')
    parser.add_argument('-p', '--path',
                        help='The path to search for files',
                        required=True,
                        dest='path')
    parser.add_argument('-e', '--ext',
                        help='The extension to search for',
                        required=True,
                        dest='extension')
    parser.add_argument('-s', '--size',
                        help='The file size to filter on in bytes',
                        type=int,
                        dest='size',
                        default=None)

    args = parser.parse_args()
    search_folder(args.path, args.extension, args.size)

if __name__ == '__main__':
    main()

Next, open up a Command Prompt (cmd.exe) in Windows and navigate to the folder that has your pysearch.py file in it. To turn the Python code into a binary executable, you need to run the following command:

pyinstaller pysearch.py

If Python isn’t on your Windows path, you may need to type out the full path to pyinstaller to get it to run. It will be located in a Scripts folder wherever your Python is installed on your system.

When you run that command, you will see some output that will look similar to the following:

6531 INFO: PyInstaller: 3.6
6576 INFO: Python: 3.8.2
6707 INFO: Platform: Windows-10-10.0.10586-SP0
6828 INFO: wrote C:\Users\mike\AppData\Local\Programs\Python\Python38-32\pysearch.spec
6880 INFO: UPX is not available.
7110 INFO: Extending PYTHONPATH with paths
['C:\\Users\\mike\\AppData\\Local\\Programs\\Python\\Python38-32',
 'C:\\Users\\mike\\AppData\\Local\\Programs\\Python\\Python38-32']
7120 INFO: checking Analysis
7124 INFO: Building Analysis because Analysis-00.toc is non existent
7128 INFO: Initializing module dependency graph...
7153 INFO: Caching module graph hooks...
7172 INFO: Analyzing base_library.zip ...

PyInstaller is very verbose and will print out a LOT of output. When it is finished, you will have a dist folder with a pysearch folder inside of it. Within the pysearch folder are many other files, including one called pysearch.exe. You can try navigating to the pysearch folder in your Command Prompt and then run pysearch.exe:

C:\Users\mike\AppData\Local\Programs\Python\Python38-32\dist\pysearch>pysearch.exe
usage: PySearch [-h] -p PATH -e EXTENSION [-s SIZE]
PySearch: error: the following arguments are required: -p/--path, -e/--ext

That looks like a pretty successful build! However, if you want to give the executable to your friends, you will have to give them the entire pysearch folder as all those other files in there are also required.

You can fix that issue by passing the --onefile flag, like this:

pyinstaller pysearch.py --onefile

The output from that command is similar to the first command. This time when you go into the dist folder though, you will find a single file in there called pysearch.exe instead of a folder full of files.

Creating an Executable for a GUI

Creating an executable for a GUI is slightly different than it is for a command-line application. The reason is that the GUI is the main interface and PyInstaller’s default is that the user will be using a Command Prompt or console window. If you run either of the PyInstaller commands that you learned about in the previous section, it will successfully create your executable. However, when you go to use your executable, you will see a Command Prompt appear in addition to your GUI.

You usually don’t want that. To suppress the Command Prompt, you need to use the --noconsole flag.

To test out how this would work, grab the code for the image viewer that was created with wxPython from chapter 42 of Python 101: 2nd Edition. Here is the code again for your convenience:

# image_viewer.py

import wx

class ImagePanel(wx.Panel):

    def __init__(self, parent, image_size):
        super().__init__(parent)
        self.max_size = 240

        img = wx.Image(*image_size)
        self.image_ctrl = wx.StaticBitmap(self, 
                                          bitmap=wx.Bitmap(img))

        browse_btn = wx.Button(self, label='Browse')
        browse_btn.Bind(wx.EVT_BUTTON, self.on_browse)

        self.photo_txt = wx.TextCtrl(self, size=(200, -1))

        main_sizer = wx.BoxSizer(wx.VERTICAL)
        hsizer = wx.BoxSizer(wx.HORIZONTAL)

        main_sizer.Add(self.image_ctrl, 0, wx.ALL, 5)
        hsizer.Add(browse_btn, 0, wx.ALL, 5)
        hsizer.Add(self.photo_txt, 0, wx.ALL, 5)
        main_sizer.Add(hsizer, 0, wx.ALL, 5)

        self.SetSizer(main_sizer)
        main_sizer.Fit(parent)
        self.Layout()

    def on_browse(self, event):
        """
        Browse for an image file
        @param event: The event object
        """
        wildcard = "JPEG files (*.jpg)|*.jpg"
        with wx.FileDialog(None, "Choose a file",
                           wildcard=wildcard,
                           style=wx.ID_OPEN) as dialog:
            if dialog.ShowModal() == wx.ID_OK:
                self.photo_txt.SetValue(dialog.GetPath())
                self.load_image()

    def load_image(self):
        """
        Load the image and display it to the user
        """
        filepath = self.photo_txt.GetValue()
        img = wx.Image(filepath, wx.BITMAP_TYPE_ANY)

        # scale the image, preserving the aspect ratio
        W = img.GetWidth()
        H = img.GetHeight()
        if W > H:
            NewW = self.max_size
            NewH = self.max_size * H / W
        else:
            NewH = self.max_size
            NewW = self.max_size * W / H
        img = img.Scale(NewW,NewH)

        self.image_ctrl.SetBitmap(wx.Bitmap(img))
        self.Refresh()


class MainFrame(wx.Frame):

    def __init__(self):
        super().__init__(None, title='Image Viewer')
        panel = ImagePanel(self, image_size=(240,240))
        self.Show()


if __name__ == '__main__':
    app = wx.App(redirect=False)
    frame = MainFrame()
    app.MainLoop()

To turn this into an executable, you would run the following PyInstaller command:

pyinstaller.exe image_viewer.py --noconsole

Note that you are not using the --onefile flag here. Windows Defender will flag GUIs that are created with the --onefile as malware and remove it. You can get around that by not using the --onefile flag or by digitally signing the executable. Starting in Windows 10, all GUI applications need to be signed or they are considered malware.

Microsoft has a Sign Tool you can use, but you will need to purchase a digital certificate or create a self-signed certificate with Makecert, a .NET tool or something similar.

Wrapping Up

There are lots of different ways to create an executable with Python. In this article, you used PyInstaller. You learned about the following topics:

  • Installing PyInstaller
  • Creating an Executable for a Command-Line Application
  • Creating an Executable for a GUI

PyInstaller has many other flags that you can use to modify its behavior when generating executables. If you run into issues with PyInstaller, there is a mailing list that you can turn to. Or you can search with Google and on StackOverflow. Most of the common issues that crop up are covered either in the PyInstaller documentation or are easily discoverable through searching online.

Would you like to learn more about Python?

Python 101 – 2nd Edition

Purchase now on Leanpub or Amazon