Python Memory Profiling with memory_profiler
Learn how to use memory_profiler to find memory leaks and optimize Python code by tracking RAM usage line by line, with practical examples and tips for real-world debugging.
Python Memory Profiling: Find Memory Leaks Like a Pro
Ever had a Python script that starts fast but gets slower over time? Or a web app that crashes after serving a few users? Chances are, you've got a memory problem on your hands. Let me show you how to hunt them down using memory_profiler — a tool that's saved my hide more times than I'd like to admit.
What exactly is memory_profiler?
Think of it as a GPS tracker for your Python code's memory usage. It watches how much RAM each line in your function consumes, helping you spot the memory hogs instantly. It's not just about finding leaks — it's about understanding where your program's memory actually goes.
Getting started with memory_profiler
First, install it:
pip install memory_profiler
Now, let's see it in action. Consider a function that processes customer data for Pythonskillset's analytics dashboard:
from memory_profiler import profile
@profile
def process_user_data():
users = []
for i in range(100000):
user = {
'id': i,
'name': f'User_{i}',
'email': f'user{i}@example.com',
'purchases': [j for j in range(10)]
}
users.append(user)
total_purchases = sum(len(u['purchases']) for u in users)
return total_purchases
process_user_data()
When you run this script, you'll get a line-by-line breakdown like this:
Line # Mem usage Increment Line Contents
================================================
5 22.1 MiB 22.1 MiB @profile
6 def process_user_data():
7 22.1 MiB 0.0 MiB users = []
8 22.2 MiB 0.1 MiB for i in range(100000):
9 41.5 MiB 19.3 MiB user = {...}
10 41.5 MiB 0.0 MiB users.append(user)
11 190.2 MiB 148.7 MiB purchases list
That "Increment" column is gold. It shows you exactly where each line's memory adds up. See the spike at line 11? That's the nested list comprehension eating 148 MB. Might be time to reconsider that approach.
Practical tips that actually work
Use generator expressions instead of lists when you don't need all data at once. Compare these two:
# Memory hungry version
big_list = [i * i for i in range(10000000)]
# Memory friendly version
big_gen = (i * i for i in range(10000000))
The generator version uses near-zero memory until you actually iterate. For Pythonskillset's real-time data processing pipeline, this switch cut memory usage by 80%.
Profile your decorator functions carefully — they wrap around your main function, so memory tracking can get confusing. Always put the @profile decorator on the innermost function you want to analyze.
Watch out for circular references. Python's garbage collector usually handles them, but they can still cause memory bloat in long-running processes. A common pattern that causes this:
class Node:
def __init__(self, parent=None):
self.parent = parent
self.children = []
if parent:
parent.children.append(self)
Each node holds a reference to its parent, and the parent holds a reference to its child — classic circular trap.
Real-world example: Debugging a leaked connection
Last month, Pythonskillset's deployment had a memory leak that crashed servers every 48 hours. Here's how memory_profiler helped:
@profile
def handle_request():
db_connection = create_connection()
result = db_connection.query("SELECT * FROM users")
# Forgot to close connection!
return process(result)
The profile showed memory increasing by 50 MB per request with no release. A simple db_connection.close() in a finally block fixed everything. Could have caught it in minutes instead of days.
Common pitfalls to avoid
- Don't profile in production directly. The profiling itself adds overhead. Instead, profile in staging or with a sample of real traffic.
- Remember that system memory includes Python's interpreter overhead. Your 100 MB script might show 150 MB total — Python itself uses memory too.
- Line profiler works best on single functions. For complex codebases, profile individual modules first, then drill down into problematic ones.
Memory profiling isn't just a debugging tool — it's your guide to writing efficient, scalable Python. The next time your app starts feeling sluggish, reach for memory_profiler before you start guessing. Your code (and your users) will thank you.
Comments
Questions, corrections, and tips stay visible for everyone reading this page.
Join the discussion
No comments yet
Be the first to leave a note — it helps the next reader.