Introduction
Once cProfile identifies a hot function, you need to know which specific lines within it are slow. The line_profiler package measures execution time for every line in decorated functions, giving you the precision needed to target optimizations at exactly the right code.
Key Concepts
line_profiler: A third-party package that hooks into Python's tracing mechanism to measure per-line execution time within decorated functions.kernprof: The command-line tool included withline_profilerthat runs your script and collects per-line timing data.@profiledecorator: A magic decorator injected bykernprof— you do not import it, just use it.- Output columns: Hits (times executed), Time (total microseconds), Per Hit (microseconds per execution), % Time (percentage of function total).
Real World Context
cProfile tells you that parse_response() takes 800ms, but the function is 40 lines long. Line profiling reveals that line 23 (a regex match) takes 600ms while everything else is fast. Now you know exactly what to optimize.
Deep Dive
Installation and Basic Usage
Install the package:
bashpip install line_profiler
Mark functions you want to profile with the @profile decorator (injected automatically by kernprof):
python# my_script.py @profile def process_data(items): results = [] for item in items: cleaned = item.strip().lower() if cleaned not in seen: results.append(cleaned) return results process_data([" Hello ", "World ", " hello", "TEST"] * 10000)
Run with kernprof:
bashkernprof -l -v my_script.py
The -l flag enables line-by-line profiling and -v displays results immediately.
Reading the Output
Typical output looks like this:
Line # Hits Time Per Hit % Time Line Contents
==============================================================
3 @profile
4 def process_data(items):
5 1 2.0 2.0 0.0 results = []
6 40001 15234.0 0.4 8.2 for item in items:
7 40000 58901.0 1.5 31.7 cleaned = item.strip().lower()
8 40000 62453.0 1.6 33.6 if cleaned not in seen:
9 10000 49234.0 4.9 26.5 results.append(cleaned)
10 1 1.0 1.0 0.0 return results
The critical column is % Time. Here, strip().lower() and the membership check together account for 65% of execution time. These are your optimization targets.
Programmatic Usage
You can use line_profiler without kernprof for integration into test suites or notebooks:
pythonfrom line_profiler import LineProfiler def target_function(data): result = [] for item in data: result.append(item ** 2) return sum(result) profiler = LineProfiler() profiler.add_function(target_function) wrapped = profiler(target_function) wrapped(range(100_000)) profiler.print_stats()
This is especially useful when you want to profile functions you cannot easily decorate in source code, or when running in Jupyter notebooks.
Profiling Multiple Functions
You can profile several related functions in one run:
pythonfrom line_profiler import LineProfiler profiler = LineProfiler() profiler.add_function(parse_header) profiler.add_function(parse_body) profiler.add_function(validate) wrapped_main = profiler(process_request) wrapped_main(request_data) profiler.print_stats()
All three functions will appear in the output if they were called during execution.
Common Pitfalls
- Leaving
@profilein production code: The@profiledecorator is injected bykernprofat runtime. If you leave it in source code and run withoutkernprof, you getNameError: name 'profile' is not defined. Remove decorators before committing or add a dummy:try: profile except NameError: profile = lambda f: f. - Profiling functions that are too short: Line profiler overhead per line execution is measurable. Functions called millions of times with 2-3 lines will show distorted results. Profile the caller instead.
- Ignoring the 'Hits' column: A line with low Per Hit but high Hits may dominate total time. Always check both columns.
Best Practices
- Use cProfile first to identify hot functions, then apply line_profiler only to those functions.
- Focus optimization effort on lines with the highest % Time — even a 50% improvement on a 1% line yields only 0.5% total improvement.
- Use programmatic
LineProfilerin Jupyter notebooks and test suites for reproducible profiling workflows.
Summary
line_profilerwithkernprof -l -v script.pyprovides per-line execution time within decorated functions.- The output shows Hits, Time, Per Hit, and % Time for each line — focus on % Time to identify optimization targets.
- Use the programmatic
LineProfilerAPI for integration into notebooks and test frameworks. - Always combine with cProfile: first find hot functions (cProfile), then find hot lines (line_profiler).
Code Examples
from line_profiler import LineProfiler
def compute_stats(data):
"""Compute basic statistics on a list of numbers."""
n = len(data)
mean = sum(data) / n
variance = sum((x - mean) ** 2 for x in data) / n
std_dev = variance ** 0.5
sorted_data = sorted(data)
median = sorted_data[n // 2]
return {'mean': mean, 'std': std_dev, 'median': median}
# Profile it
profiler = LineProfiler()
profiler.add_function(compute_stats)
wrapped = profiler(compute_stats)
import random
data = [random.gauss(0, 1) for _ in range(500_000)]
wrapped(data)
profiler.print_stats()