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
356: Profiling Code with cProfile
Imagine you're running a professional kitchen during a dinner rush. The food is getting to the tables too slowly, and the customers are getting grumpy. You know the kitchen is the bottleneck, but you don't know why. Do you just tell every single chef to "work faster"? Of course not. That's a waste of energy. Instead, you stand back with a stopwatch and observe. You notice the grill chef is lightning fast, but the prep cook is spending ten minutes chopping parsley for every single plate. You've found the bottleneck. Now you can actually fix the problem—maybe by prepping the parsley in advance—without bothering the grill chef.
In Python, cProfile is that stopwatch. When your code feels sluggish, the biggest mistake I see developers make is "guessing" where the lag is. They'll rewrite a function that looks complex but only takes 2% of the total execution time, while the real culprit—maybe a hidden nested loop or a slow API call—continues to eat up 80% of the runtime. We call this premature optimization, and it's a great way to waste a Tuesday afternoon.
Stop Guessing Where the Lag Is
Let's look at a real scenario. Say you've written a script to filter a large list of user IDs against a "blacklist." It works, but it's taking forever as the list grows.
import cProfile
def check_blacklist(user_ids, blacklist):
valid_users = []
for user in user_ids:
if user not in blacklist: # This is the hidden killer
valid_users.append(user)
return valid_users
def run_process():
# Simulate 10,000 users and a blacklist of 5,000
users = list(range(10000))
blacklist = list(range(5000))
check_blacklist(users, blacklist)
# Instead of just calling run_process(), we wrap it in cProfile
cProfile.run('run_process()')
If you run this, Python won't just execute the code; it'll spit out a table. At first glance, the table looks like a wall of text, but I only ever care about a few specific columns.
Reading the Profiler's Output
When you see the results, you'll notice columns like ncalls, tottime, and cumtime. Here is how I translate those into English:
- ncalls: How many times the function was called. If this number is in the millions and you didn't expect it to be, you've probably got a loop problem.
- tottime: The total time spent inside this function, excluding time spent in other functions it called. This is the "holy grail" for finding bottlenecks.
- cumtime: The total time spent in this function including all the sub-functions it called. This tells you which high-level process is slow, but not necessarily which specific line of code is the culprit.
In our blacklist example, you'll see that check_blacklist has a massive tottime. Why? Because checking if user not in blacklist on a list takes O(n) time. Python has to scan the entire list over and over again. I've spent way too many hours of my life fixing "slow code" only to realize I was just using a list where I should have used a set.
Turning Data into Speed
Once cProfile points the finger at check_blacklist, the fix becomes obvious. I change the blacklist from a list to a set. In Python, set lookups are O(1)—they're nearly instantaneous regardless of the size of the set.
def check_blacklist_fast(user_ids, blacklist_set):
# Converting to a set once is much cheaper than
# scanning a list 10,000 times.
return [user for user in user_ids if user not in blacklist_set]
If you profile the updated version, you'll see the tottime for that function drop from seconds to milliseconds. That's the power of profiling: you stop guessing and start measuring.
📋 Practical Task
Profiling and Optimizing the Log Analyzer
You have been given a piece of code that analyzes system logs to find unique error messages. However, as the log file grows, the script becomes unusable. Your goal is to use cProfile to identify the exact function causing the slowdown and then optimize it.
The Code:
import cProfile
def find_unique_errors(logs):
unique_errors = []
for log in logs:
if "ERROR" in log:
# We only want to keep the error message if we haven't seen it yet
if log not in unique_errors:
unique_errors.append(log)
return unique_errors
def simulate_logs():
# Generating 20,000 log entries with many duplicates
base_errors = ["ERROR: Connection Timeout", "ERROR: Disk Full", "ERROR: Auth Failed"]
logs = [base_errors[i % 3] + f" - line {i}" for i in range(20000)]
# To make it really slow, let's add some actual duplicates
logs = logs * 2
return logs
def main():
logs = simulate_logs()
find_unique_errors(logs)
cProfile.run('main()')
Your Task:
- Run the code and analyze the
cProfileoutput. Which function is consuming the mosttottime? - Identify the line inside that function causing the bottleneck.
- Rewrite the
find_unique_errorsfunction using a more efficient data structure to track seen errors. - Run
cProfileagain to verify that thetottimefor that function has significantly decreased.
There are no comments for now.