Python Notes
Comprehensive guide to Python memory management, reference counting, garbage collection, memory profiling, optimization techniques, and best practices with Hindi explanations.
Introduction
Python memory management happens automatically, but as an advanced developer it's important to understand how Python allocates and deallocates memory. This knowledge helps debug memory leaks, optimize performance, and write memory-efficient code.
Python Memory ke Main Components:
- Reference Counting — primary memory management
- Garbage Collector — circular reference detection
- Memory Pools (pymalloc) — small object optimization
2. Object Identity and Small Integer Caching
True
True
False
False
True
True
True
int(1): 28 bytes
int(10**100): 72 bytes
list[]: 56 bytes
list[10]: 136 bytes
dict{}: 64 bytes
str 'hi': 51 bytes3. Garbage Collection — Circular References
import gc
# Circular reference — reference counting alone can't handle this!
class Node:
def __init__(self, data):
self.data = data
self.next = None
# Create circular reference
a = Node("A")
b = Node("B")
a.next = b # a -> b
b.next = a # b -> a (circular!)
del a # ref count doesn't reach 0 (b still points to a)
del b # ref count doesn't reach 0 (a still points to b)
# Without GC, this would be a memory leak!
# Python's GC detects and cleans circular references
gc.collect() # Force garbage collection
print(f"Collected {gc.collect()} objects")
# Monitoring GC
print(f"GC enabled: {gc.isenabled()}")
print(f"GC thresholds: {gc.get_threshold()}") # (700, 10, 10)
# Generation 0: collect when 700 new objects created
# Generation 1: collect after gen0 collected 10 times
# Generation 2: collect after gen1 collected 10 times
# Manual GC control
gc.disable() # Turn off automatic GC
# ... performance-critical code ...
gc.enable() # Re-enable
gc.collect() # Force collect onceCollected 4 objects GC enabled: True GC thresholds: (700, 10, 10)
4. Memory Profiling with tracemalloc
Current: 1024.56 KB Peak: 1025.12 KB Top 10 memory consumers: <stdin>:5: size=394 KiB, count=10001, average=40 B
Memory increase: <stdin>:8: size=38.1 MiB (+38.1 MiB), count=1 (+1), average=38.1 MiB <stdin>:5: size=576 B (+576 B), count=1 (+1), average=576 B
📝 Hindi Explanation
tracemalloc is a built-in memory profiling tool in Python 3.4+.take_snapshot()captures a snapshot of current memory allocations.compare_to()shows the difference between two snapshots — this is the most effective way to detect memory leaks.
5. sys.getsizeof — Object Size
None: 16 bytes
True: 28 bytes
int(0): 28 bytes
float: 24 bytes
str '': 49 bytes
str 'a': 50 bytes
list[]: 56 bytes
tuple(): 40 bytes
dict{}: 64 bytes
set(): 216 bytes
List [1,2,3]: 120 bytes
Total size of nested dict: 654 bytes6. Weak References
Creating Strong Strong ref count: 1 Weak ref alive: True Object name: Strong Deleting Strong Weak ref alive after del: False Creating Cached1 Cache hit: <__main__.ExpensiveObject object> Deleting Cached1 Cache after del: None
7. Memory Optimization Techniques
Regular: 200 bytes Slotted: 56 bytes Python list: 8856 bytes array.array: 4120 bytes
📝 Hindi Explanation
__slots__is a powerful optimization. Regular Python objects have their own__dict__(dictionary) per instance which consumes memory.__slots__replaces the dict with a fixed-size struct, saving 40-50% memory when creating many instances.
8. Memory Leaks — Detection and Prevention
9. Memory Profiling Best Practices
Top 5 memory consumers:
#1: script.py:15 — 820.3 KB
result = {i: list(range(100)) for i in range(1000)}
#2: script.py:15 — 39.1 KB
Total allocated: 862.4 KB10. Object Lifecycle
import gc
class Tracked:
instances = []
def __init__(self, name):
self.name = name
Tracked.instances.append(weakref.ref(self))
print(f"Created: {self.name}")
def __del__(self):
print(f"Destroyed: {self.name}")
import weakref
# Lifecycle demonstration
print("=== Creating objects ===")
a = Tracked("A")
b = Tracked("B")
print("\n=== Creating reference ===")
c = a # Same object as a
print("\n=== Deleting original reference ===")
del a
# a deleted but object still alive (c references it)
print(f"Object A alive: {c.name}")
print("\n=== Deleting last reference ===")
del c
# Now object A is garbage collected
gc.collect()
print("\n=== Deleting B ===")
del b
gc.collect()
print("All objects cleaned up")=== Creating objects === Created: A Created: B === Creating reference === === Deleting original reference === Object A alive: A === Deleting last reference === Destroyed: A === Deleting B === Destroyed: B All objects cleaned up
11. Summary and Cheatsheet
Object size: 64 bytes (shallow) Ref count: 1 GC counts: (595, 2, 1) GC thresholds: (700, 10, 10) Collected 0 objects
| Tool | Purpose |
|---|---|
sys.getsizeof() | Object size in bytes |
sys.getrefcount() | Reference count |
gc.collect() | Force garbage collection |
gc.get_count() | Generation object counts |
tracemalloc | Memory allocation tracing |
weakref.ref() | Weak references |
__slots__ | Reduce instance memory |
array.array | Typed array (smaller than list) |
Memory Optimization Priority: Use generators > lists, use__slots__for many small objects, useweakreffor caches, profile withtracemallocbefore optimizing.
⚠️ Common Mistakes
❌ Mistake 1: Relying Only on del for Memory Cleanup
❌ Mistake 2: Ignoring Circular References
# ❌ Wrong — Circular reference without weakref causes hidden memory leaks
class Parent:
def __init__(self):
self.child = Child(self)
class Child:
def __init__(self, parent):
self.parent = parent # Strong back-reference = circular!
# ✅ Right — Use weakref for back-references
import weakref
class Child:
def __init__(self, parent):
self.parent = weakref.ref(parent) # Weak reference breaks cycle❌ Mistake 3: Using Lists Where Generators Work
❌ Mistake 4: Not Using __slots__ for Data-Heavy Classes
❌ Mistake 5: Unbounded Caches Without Size Limits
❌ Mistake 6: Confusing sys.getsizeof() with Total Memory
❌ Mistake 7: Not Profiling Before Optimizing
✅ Key Takeaways
- 🔢 Reference Counting is Python's primary memory management — when count reaches 0, memory is freed immediately
- 🔄 Garbage Collector detects circular references that reference counting misses — it uses a generational algorithm (gen0, gen1, gen2)
- 📦 Small Integer Caching (-5 to 256) and String Interning save memory — this is why
100 is 100evaluates to True - 🛠️ Use
__slots__when creating many instances — eliminating__dict__saves 40-50% memory - 🌊 Prefer Generators over lists when you don't need all data in memory at once — lazy evaluation keeps memory constant
- 🔗 Use
weakreffor caches and back-references — it doesn't prevent the object from being garbage collected - 📊
tracemalloccan be used in production code to find memory leaks — snapshot comparison is the most powerful technique - 💡 Profile first, optimize later — optimization without measurement is premature and makes code unnecessarily complex
- 🧹 Call
gc.collect()manually after critical sections — especially when circular references may have been created - ⚡ Use
array.arrayfor homogeneous numeric data — it uses 4-5x less memory than a Python list
❓ FAQ
Q1: If Python manages memory automatically, why should we understand it manually?
Answer: Yes, Python provides automatic memory management, but in real-world applications, memory leaks, performance bottlenecks, and OOM (Out of Memory) errors require deep understanding of memory internals for diagnosis and resolution.
Q2: Does the del statement actually free memory?
Answer: del only removes the variable's reference — it doesn't delete the object. If the object's reference count reaches 0 after del, then memory is freed. If other references exist, the object remains in memory.
Q3: When does the Garbage Collector run? Can we control it?
Answer: GC runs automatically when generation thresholds are exceeded (default: 700, 10, 10). You can manually trigger with gc.collect(), disable with gc.disable(), or change thresholds with gc.set_threshold().
Q4: What is a circular reference and why is it dangerous?
Answer: A circular reference occurs when two or more objects reference each other (A → B → A). Reference counting cannot free these objects since their count never reaches 0. The garbage collector handles them, but if __del__ is defined, they may end up in gc.garbage (uncollectable).
Q5: When should you use __slots__ and when not?
Answer: Use __slots__ when: (1) Many instances are being created (thousands/millions), (2) Attributes are fixed and dynamic addition isn't needed, (3) Memory optimization is critical. Don't use when: inheritance with dict-based classes, need for dynamic attributes, or prototyping.
Q6: What is the difference between weakref and a normal reference?
Answer: A normal (strong) reference increments the object's reference count — the object won't be garbage collected as long as a strong reference exists. weakref doesn't increment the count — when all strong references disappear, the object is garbage collected even if weak references exist.
Q7: Is memory profiling safe in production?
Answer: tracemalloc is lightweight and can be used in production, but adds ~5-10% performance overhead. Best practice: enable only in development and staging. In production, use it temporarily for debugging specific memory issues.
Q8: How do you detect memory leaks in Python?
Answer: Steps: (1) Start monitoring with tracemalloc.start(), (2) Take snapshots at two time points with take_snapshot(), (3) Use compare_to() to see the difference — growing allocations indicate leaks.
🎯 Interview Questions
Q1: How does memory management work in Python? Explain the role of reference counting and garbage collection.
Answer: Python (CPython) has a dual memory management system:
- Reference Counting (Primary): Every object maintains a count of how many references point to it. When the count reaches 0, memory is freed immediately. This is fast and deterministic.
- Garbage Collector (Secondary): Uses a generational algorithm (3 generations: gen0, gen1, gen2). It specifically detects and cleans up circular references that reference counting cannot handle.
- Memory Pools (pymalloc): An optimized allocator for small objects (≤512 bytes) that reduces system malloc overhead.
Q2: What is a circular reference? How does Python handle it? Explain with an example.
Answer: A circular reference forms when objects reference each other directly or indirectly. Example: a.ref = b and b.ref = a. Reference counting can't free them (count never reaches 0). Python's GC uses a cycle-detecting algorithm that traverses object graphs to find and break these cycles.
Q3: When should gc.collect() be called manually? What are the drawbacks?
Answer: Manual gc.collect() call karein jab:
- After large batch processing where circular references are possible
- Before memory-critical operations to maximize free memory
- In testing/profiling when deterministic behavior is needed
Drawbacks: GC pause can freeze the application (stop-the-world), especially when many objects need scanning. For real-time applications (games, trading systems), disable automatic GC and collect at safe points.
Q4: How does __slots__ optimize memory? Explain the internal mechanism.
Answer: Normal Python objects store data in __dict__ (a dictionary) — this is flexible but allocates a separate dict for every instance. __slots__ replaces the dict with a fixed-size C struct with direct pointers, saving ~40-50% memory per instance.
Q5: Why does sys.getrefcount() return 1 more than the actual count?
Answer: When calling sys.getrefcount(obj), obj is passed as a function argument — this creates a temporary reference. So the returned count is always actual_count + 1.
Q6: How do you identify and fix a memory leak in Python? Explain with a real-world scenario.
Answer: Scenario: A web server's RAM usage gradually increases over time.
Identification Steps:
- Use
tracemallocsnapshot comparison — find growing allocations - Monitor object count over time with
gc.get_objects() - Use
objgraph.show_growth()to see which object types are increasing
Common Causes & Fixes:
- Unbounded caches:
lru_cache(maxsize=N)yaWeakValueDictionaryuse karo - Circular references: Use
weakrefin parent-child relationships - Global accumulators: Periodic cleanup ya bounded collections use karo
- Event listener leaks: Always unsubscribe/deregister when done
Q7: What is generational garbage collection? How many generations does Python have and how do they work?
Answer: Generational GC is based on the "weak generational hypothesis" — most objects die young. Python has 3 generations:
- Generation 0: New objects go here. Threshold 700 — when 700 new allocations happen, gen0 is collected.
- Generation 1: Gen0 survivors are promoted here. Threshold 10 — after 10 gen0 collections, gen1 is collected.
- Generation 2: Long-lived objects. Threshold 10 — after 10 gen1 collections, gen2 is collected.
The young generation is scanned frequently (fast, few objects), the old generation rarely (slow, many objects but stable). View thresholds with gc.get_threshold(), modify with gc.set_threshold().
Q8: Explain the practical use case of the weakref module with code.
Answer: Use Case: Object Cache — Hold objects in cache without extending their lifetime.
When other code releases its reference to an image object, it will automatically be removed from the cache — no memory leak! Useful for observer pattern, tree structures (parent-child), and caching.
Q9: What is the difference between tracemalloc and sys.getsizeof()? When should you use which?
Answer:
| Feature | sys.getsizeof() | tracemalloc |
|---|---|---|
| Scope | Single object (shallow) | Entire program allocations |
| Overhead | Zero | 5-10% runtime cost |
| Depth | Only immediate object, not referenced objects | Tracks all allocations with tracebacks |
| Use Case | Quick size check | Memory leak detection, profiling |
| Live tracking | No | Yes (snapshots over time) |
Use sys.getsizeof() for quick checks. Use tracemalloc when you need to detect memory leaks or identify which code lines consume the most memory.
Q10: What is Python's memory pool (pymalloc) and how does it work?
Answer: pymalloc is CPython's small-object allocator optimized for objects ≤512 bytes. Instead of calling system malloc() directly, it maintains memory arenas (256KB chunks) divided into pools (4KB) and blocks. This reduces allocation overhead and fragmentation for the many small objects Python creates.
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for Python Memory Management.
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this Python Master Course topic.
Search Terms
python-master-course, python master course, python, master, course, advanced, memory, management
Related Python Master Course Topics