1. Memory Management: Python’s “Invisible Steward”
Python memory management operates through automatic garbage collection and reference counting, silently handling:
- • Object creation and destruction
- • Memory leak prevention
- • Circular reference handling
- • Optimization of large memory objects
Core Objective: To achieve a balance between development efficiency and runtime performance, allowing developers to avoid manual memory management.
2. Basics: Principles of Memory Allocation
1. Object Lifecycle
# Object creation
a = [1,2,3]
# Reference count increases
b = a
# Reference count decreases
del b
# Reference goes to zero → Object is destroyed
del a
2. Viewing Reference Count
import sys
a = [1,2,3]
print(sys.getrefcount(a)) # Output: 2 (including temporary references)
def show_refcount(obj):
print(sys.getrefcount(obj))
show_refcount(a) # Output: 3 (function call creates a temporary reference)
3. Advanced: Garbage Collection Mechanisms
1. Generational Collection Strategy
- • Generation 0: Newly created objects
- • Generation 1: Objects that survived one GC cycle
- • Generation 2: Objects that survived multiple GC cycles
import gc
# Manually trigger GC
gc.collect()
# View the count of objects in each generation
print(gc.get_count()) # Output: (Generation 0 count, Generation 1 count, Generation 2 count)
2. Circular Reference Handling
# Create circular reference
class Node:
def __init__(self):
self.next = None
a = Node()
b = Node()
a.next = b
b.next = a
# Manually break circular reference
del a.next
del b.next
3. Weak Reference Optimization
import weakref
class HeavyObject:
def __init__(self):
self.data = [x for x in range(10**6)]
# Use weak references to avoid memory leaks
cache = weakref.WeakValueDictionary()
cache[key] = HeavyObject() # Object is automatically reclaimed when key is deleted
4. Common Error Debugging Guide
Error 1: Memory Leak
# Error: Global list keeps growing
global_list = []
def process_data():
data = generate_large_data()
global_list.append(data) # List grows indefinitely causing memory leak
# Fix: Use generators or limit list length
def safe_process():
for chunk in generate_chunks():
yield process_chunk(chunk)
Error 2: Unnecessary Object Copying
# Error: Frequent string concatenation
s = ""
for chunk in read_file():
s += chunk # Each concatenation creates a new string
# Fix: Use join or generators
s = "".join(read_file())
# or
def stream_data():
for chunk in read_file():
yield chunk
Error 3: Misuse of Global Variables
# Error: Global cache has no limit
global_cache = {}
def fetch_data(key):
if key not in global_cache:
global_cache[key] = heavy_computation(key)
return global_cache[key]
# Fix: Use LRU cache
from functools import lru_cache
@lru_cache(maxsize=128)
def optimized_fetch(key):
return heavy_computation(key)
5. Practical Scenarios: Optimizing Memory Usage
Scenario 1: Processing Large Files
def process_large_file(file_path):
with open(file_path, 'r') as f:
for line in f: # Read line by line, memory usage remains constant
process(line)
Scenario 2: Implementing a Cache System
import weakref
class SmartCache:
def __init__(self):
self.cache = weakref.WeakValueDictionary()
def get(self, key, default=None):
return self.cache.get(key, default)
def set(self, key, value):
self.cache[key] = value
# Usage example
cache = SmartCache()
cache.set("config", load_config()) # Automatically reclaimed when no other references exist
Scenario 3: Memory Analysis Tool
import tracemalloc
tracemalloc.start()
# Execute code to analyze...
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('lineno')
for stat in top_stats[:10]:
print(stat)