Python
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Data Types
-
Section 3: Collections
-
39: Set Operations: Union, Intersection, Difference
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Turtle Graphics and Early Practice Projects
-
Section 7: Working with Files and I/O
-
Section 8: Regular Expressions
-
Section 9: Object-Oriented Python
-
Section 10: Error Handling
-
Section 11: Modules and Packages
-
Section 12: Iterators, Generators, and Functional Tools
-
Section 13: Decorators and Metaprogramming
-
Section 14: Concurrency and Parallelism
-
Section 15: Working with Dates, Times, and Numbers
-
Section 16: Standard Library Deep Dive I: Data Structures
-
Section 17: Standard Library Deep Dive II: System and Introspection
-
Section 18: Standard Library Deep Dive III: Security and Encoding
-
Section 19: Standard Library Deep Dive IV: Text and Data Utilities
-
Section 20: Networking and Web Basics
-
Section 21: Working with Databases
-
Section 22: Testing and Quality
-
Section 23: Advanced Typing
-
Section 24: Context Managers and Resource Handling
-
Section 25: Text, Unicode, and Binary Data
-
Section 26: More Functional and Iteration Tools
-
Section 27: Data Validation and Configuration
-
Section 28: Working with Images and Media
-
Section 29: Property-Based and Documentation Testing
-
Section 30: Packaging and Deployment
-
Section 31: Performance and Internals
-
Section 32: Design Patterns in Python
-
Section 33: GUI Programming
-
Section 34: Security Basics
-
Section 35: Data Structures and Algorithms
-
Section 36: Practical Projects
-
Section 37: Capstone Projects
-
Section 38: Interview and Algorithm Practice
-
Section 39: Writing Idiomatic Python
353: Freezing Applications with PyInstaller
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
psutillibrary 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
--onefileflag 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.
There are no comments for now.