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
359: Line-by-Line Profiling with line_profiler
I see this all the time: a developer notices their script is lagging, runs cProfile, sees that a function called process_data() is taking 80% of the execution time, and then spends the next three hours guessing which part of that function is the culprit. They start haphazardly replacing for loops with list comprehensions or swapping libraries, hoping for the best.
Thinking cProfile reveals the "Why"
Here is the reality: cProfile is a function-level profiler. It tells you which function is slow, but it's blind to the internal logic of that function. If you have a 50-line function, cProfile just gives you one total number for the whole block. It's like a doctor telling you "your torso hurts" without being able to tell if it's your lungs, your heart, or a kidney stone. You know where the problem is generally, but you don't know the exact line of code causing the bleed.
# A typical scenario where cProfile fails you
def process_data(items):
# Is the bottleneck here?
cleaned = [i.strip().lower() for i in items]
# Or here?
results = []
for item in cleaned:
if item in some_massive_blacklist: # O(n) lookup in a list!
continue
results.append(heavy_computation(item))
return results
If process_data shows up as the bottleneck, you're left guessing. Is heavy_computation the problem? Or is it the in some_massive_blacklist check? Guessing is a waste of your engineering time.
Zooming in with line_profiler
This is where line_profiler comes in. Instead of aggregating time by function, it hooks into the Python interpreter to track exactly how many times each individual line is executed and how much time is spent on each one. It transforms your profiling from a "best guess" into a "surgical strike."
To get started, you'll need to install it via pip: pip install line_profiler. But here is the part that trips people up: you don't typically import it into your script. Instead, you use a special decorator called @profile and run your script using a specific command-line tool called kernprof.
Let's look at a concrete example. I've written a small script that simulates processing a batch of IDs. I suspect there's a bottleneck, but I want proof.
# script.py
@profile
def identify_bottleneck():
data = list(range(10000))
# Line A: Simple list transformation
squared = [x**2 for x in data]
# Line B: An inefficient search pattern
results = []
for x in squared:
if x not in range(1000000): # This is surprisingly slow
results.append(x)
return results
if __name__ == "__main__":
identify_bottleneck()
Notice that @profile isn't imported from anywhere. If you try to run this with python script.py, you'll get a NameError. That's because kernprof (the runner) injects the profile function into the global namespace at runtime. It's a bit weird, but it keeps your production code clean of profiling imports.
To run this, you use the following commands in your terminal:
# 1. Run the profiler (the -l flag tells it to do line-by-line)
kernprof -l -v script.py
The -v flag is critical; it tells kernprof to print the results to your console immediately after the script finishes. You'll see a table with columns like Hits, Time, Per Hit, and % Time. When you look at that % Time column, the answer becomes obvious. You'll likely see that the if x not in range(...) line is consuming 99% of the function's time, while the list comprehension is negligible.
I've found that using this tool is the single fastest way to move from "I think this is slow" to "I know exactly which line to rewrite." Don't guess. Profile.
📋 Practical Task
Optimizing a Heavy Log Parser
You have been handed a script that parses a large system log file to find unique error codes. The current implementation is painfully slow. Your task is to use line_profiler to find the specific line causing the slowdown and fix it.
The Setup:
Create a file named log_parser.py with the following code:
import time
# Imagine this is a large list of error codes from a log file
LOG_DATA = ["ERR_001", "ERR_002", "ERR_001", "ERR_003", "ERR_002"] * 10000
@profile
def extract_unique_errors(logs):
unique_errors = []
for code in logs:
if code not in unique_errors:
unique_errors.append(code)
# Simulate some final processing
final_report = sorted(unique_errors)
return final_report
if __name__ == "__main__":
extract_unique_errors(LOG_DATA)
Your Objectives:
- Install
line_profiler. - Run the script using
kernprof -l -v log_parser.py. - Analyze the output table. Identify which line is spending the most time and why (think about the time complexity of searching a list).
- Rewrite the
extract_unique_errorsfunction to use a more efficient data structure (like aset) to eliminate the bottleneck. - Run the profiler again to verify that the
% Timespent on the membership check has dropped significantly.
There are no comments for now.