Skip to Content
Course content

353: Freezing Applications with PyInstaller

Click on the "Edit" button in the top corner of the screen to edit your slide content.

One of the most frustrating parts of Python development is the "it works on my machine" problem. You've written a great tool for a colleague or a client, but they don't have Python installed, or they're terrified of the command line, or they're using a different version of a library that breaks your code. This is where "freezing" comes in.

When we freeze an application, we're essentially bundling the Python interpreter, your script, and all your dependencies into a single folder or executable file. PyInstaller is the industry standard for this. It doesn't actually convert Python to C++; it just packages everything together so the end-user doesn't have to manage a virtual environment just to run your script.

Writing a PDF Page Counter

To see this in action, let's build something slightly more useful than a "Hello World." I'll write a small utility that scans a directory for PDF files and prints the total page count. I'm using PyPDF2 for this, which means we have an external dependency—the exact kind of thing that usually makes distributing scripts a pain.

import os
from PyPDF2 import PdfReader

def count_pdf_pages():
    total_pages = 0
    files = [f for f in os.listdir('.') if f.endswith('.pdf')]
    
    if not files:
        print("No PDF files found in the current directory.")
        return

    for file in files:
        try:
            reader = PdfReader(file)
            total_pages += len(reader.pages)
        except Exception as e:
            print(f"Could not read {file}: {e}")

    print(f"Total pages across {len(files)} PDFs: {total_pages}")

if __name__ == "__main__":
    count_pdf_pages()
    input("\nPress Enter to exit...") # Keep window open after execution

I added that input() at the end because if we run this as a standalone windowed app, the console will close the millisecond the script finishes, and the user won't actually see the result.

Packaging it into a Single Binary

First, I'll install PyInstaller: pip install pyinstaller. Now, the simplest way to run it is just pyinstaller script_name.py, but that creates a massive folder full of .dll files and dependencies. It's messy.

I prefer the --onefile flag. It bundles everything into a single executable. I'll run this in my terminal:

pyinstaller --onefile pdf_counter.py

PyInstaller starts analyzing the imports, finds PyPDF2 and the Python standard library, and begins stitching them together. It takes a minute or two because it's basically building a mini-filesystem inside that executable.

The "Where did my app go?" Moment

Here is where I usually trip up when I'm rushing. After the build finishes, I see a build/ folder and a dist/ folder. In my haste, I navigated into build/, found an executable, tried to run it, and it crashed immediately with a bunch of "ModuleNotFoundError" screams.

I forgot the golden rule of PyInstaller: The build/ folder is for the compiler; the dist/ folder is for the user.

The build/ directory contains temporary log files and working files used during the freezing process. The actual, shippable product is always in dist/. I moved over to the dist/ folder, ran pdf_counter.exe (on Windows), and it worked perfectly—even though I moved it to a folder with no Python installation at all.

Cleaning up the Bundle

If you're distributing this to a non-technical user, you probably don't want a black console window popping up if you've built a GUI app. While our tool is a CLI app, if this were a Tkinter or PyQt app, I'd add the --noconsole flag.

Also, PyInstaller generates a .spec file. Don't delete this if you plan on updating the app frequently. The spec file stores the configuration of your build. Instead of typing a long string of flags every time, you can just run pyinstaller pdf_counter.spec to rebuild the app using the previous settings.




📋 Practical Task

Exercise: Freeze a System Resource Logger

Your task is to create a standalone executable that logs the current CPU and Memory usage of the system to a file called sys_log.txt.

  • Requirement 1: Use the psutil library to gather the CPU percentage and virtual memory percentage.
  • Requirement 2: The script should append the current timestamp and the resource stats to the text file.
  • Requirement 3: Use PyInstaller with the --onefile flag to package the script.
  • Requirement 4: Verify the executable works by moving the resulting file from the dist/ folder to a completely different directory (or a different computer) and running it.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.