import sys
a = [1, 2, 3]
print(sys.getrefcount(a)) # includes the temporary ref from getrefcount's own argument
b = a
print(sys.getrefcount(a)) # increased by one
Topics
20
Async Generators & Async Context Managers
Command-Line Interfaces (argparse)
Comprehensions & Generators
Concurrency (asyncio/threading/multiprocessing)
Data Types & Structures
Dataclasses & NamedTuples
Debugging & Profiling
Decorators
Descriptors & Properties
Exception Handling
File I/O & Context Managers
Functions & Scope
functools & Functional Programming Tools
Iterators & the Iterator Protocol
Logging
Magic Methods & Operator Overloading
Memory Management & Garbage Collection
Metaclasses & Class Customization
Modules & Packaging
Multiple Inheritance & MRO
Memory Management & Garbage Collection
15 questions found
CPython uses automatic reference counting as its primary memory management mechanism: every object tracks how many references point to it, and when that count drops to zero, the memory is immediately deallocated. A supplementary cyclic garbage collector handles reference cycles that reference counting alone cannot free.
Real-world example
Understanding refcounting explains why a large object is freed the instant its last variable goes out of scope or is reassigned, without waiting for a garbage collection cycle.
Memory Management & Garbage Collection;Data Types & Structures
A reference cycle occurs when two or more objects reference each other (directly or indirectly), keeping their reference counts above zero even when nothing outside the cycle refers to them -- so pure reference counting never reaches zero for these objects, causing a memory leak without a separate cycle-detecting collector.
class Node:
def __init__(self):
self.parent = None
self.child = None
a, b = Node(), Node()
a.child = b
b.parent = a # cycle: a -> b -> a
del a, b # refcounts don't reach 0 due to the cycle
Real-world example
A tree data structure where each node stores both a parent and child reference forms a natural cycle that relies on the cyclic garbage collector, not refcounting, to eventually be reclaimed.
Multiple Inheritance & MRO;Memory Management & Garbage Collection
CPython's gc module divides tracked objects into three generations (0, 1, 2) based on survival: new objects start in generation 0; objects that survive a collection are promoted to the next generation. Younger generations are collected more frequently since most objects die young (the generational hypothesis), while older, longer-lived objects are scanned less often, improving overall efficiency.
import gc
print(gc.get_threshold()) # e.g., (700, 10, 10) -- collection thresholds per generation
gc.collect() # force a full collection
print(gc.get_count()) # current allocation counts per generation
Real-world example
A long-running server process periodically calls gc.collect() during idle periods to proactively reclaim cyclic garbage before it accumulates and causes memory pressure spikes.
Concurrency (asyncio/threading/multiprocessing);Debugging & Profiling
weakref.ref (and weakref.proxy) creates a reference to an object that doesn't increase its reference count, so the referenced object can still be garbage collected even while the weak reference exists (in which case it becomes None or raises ReferenceError). This is useful for caches, observer patterns, or parent-child relationships where you want a back-reference without preventing cleanup.
import weakref
class Parent:
def __init__(self):
self.children = []
class Child:
def __init__(self, parent):
self.parent = weakref.ref(parent) # doesn't keep parent alive
p = Parent()
c = Child(p)
print(c.parent()) # <Parent object> while p is alive
Real-world example
An event-listener system stores weak references to subscriber objects so subscribers can be garbage collected normally when no longer used elsewhere, instead of being kept alive forever just because they're registered as listeners.
Multiple Inheritance & MRO;Data Types & Structures
What is the sys.getsizeof() function, and what are its limitations for measuring memory usage?
Intermediatesys.getsizeof(obj) returns the memory size in bytes of the object itself, but not the objects it references -- a list of large strings might report a small size for the list container while the actual strings it points to consume much more memory elsewhere. To measure deep memory usage, you need to recursively sum referenced objects (e.g., with a library like pympler) or use sys.getsizeof carefully with a visited-set to avoid double-counting cycles.
import sys
small_list = [1, 2, 3]
print(sys.getsizeof(small_list)) # size of the list structure only
print(sys.getsizeof(small_list) + sum(sys.getsizeof(x) for x in small_list)) # closer to real usage
Real-world example
A memory-profiling script mistakenly concludes a nested data structure is small because it only calls getsizeof on the outer container, missing the megabytes referenced by inner lists.
Debugging & Profiling;Data Types & Structures
How does object interning (like small integer caching and string interning) affect memory and identity comparisons?
AdvancedCPython caches small integers (-5 to 256) and some string literals as singleton objects, so multiple variables referencing the same small int or interned string may share the same object in memory (verifiable with `is`). This is a memory optimization detail, not a guaranteed language feature -- relying on `is` for value equality of ints or strings outside interning ranges is a common bug source.
a = 100
b = 100
print(a is b) # True -- small int caching
x = 1000
y = 1000
print(x is y) # often False -- not cached, implementation-dependent
Real-world example
A subtle bug arises when code checks `if user_id is 12345:` instead of `==`, working accidentally in testing due to caching but failing in production with larger, uncached integer values.
Data Types & Structures;Debugging & Profiling
How do context managers and the `del` statement relate to timely memory cleanup versus relying on garbage collection?
Intermediate`del` removes a name binding, decrementing the referenced object's refcount immediately -- if it reaches zero, the object is freed right away via reference counting, not waiting for a GC cycle. Context managers (with __exit__) similarly guarantee deterministic, immediate cleanup of resources (file handles, locks, connections) at block exit, which is more reliable than depending on garbage collection timing for external resources.
class DatabaseConnection:
def __enter__(self):
self.conn = connect()
return self.conn
def __exit__(self, *args):
self.conn.close() # deterministic cleanup, not left to GC
with DatabaseConnection() as conn:
conn.query('SELECT 1')
Real-world example
A file-processing script explicitly deletes large intermediate data structures with `del` between processing stages to free memory immediately rather than waiting for them to fall out of scope naturally.
File I/O & Context Managers;Exception Handling
Why is __del__ (the finalizer method) discouraged for critical cleanup logic, and what problems can it cause?
Advanced__del__ is called when an object's refcount reaches zero, but its exact timing is not guaranteed -- especially with reference cycles, where objects in a cycle involving __del__ historically could never be collected (pre-3.4) or are now collected but in unpredictable order. __del__ can also resurrect objects by creating new references, complicate exception handling during interpreter shutdown, and generally should be replaced by explicit context managers or atexit handlers for reliable cleanup.
class Resource:
def __del__(self):
print('Cleaning up') # timing not guaranteed, avoid relying on this
# Better: explicit context manager
class Resource:
def __enter__(self): return self
def __exit__(self, *a): print('Cleaning up') # deterministic
Real-world example
A codebase relying on __del__ to close database connections experiences intermittent connection leaks in production because objects involved in reference cycles get collected at unpredictable, delayed times.
File I/O & Context Managers;Multiple Inheritance & MRO
How does the gc module let you inspect and debug memory leaks caused by uncollectable objects?
Intermediategc.collect() returns the number of unreachable objects it found and collected; gc.garbage lists objects the collector found uncollectable (in older Python versions, mainly ones with __del__ methods in cycles). gc.set_debug(gc.DEBUG_LEAK) and get_objects()/get_referrers() help trace what's holding references to unexpectedly long-lived objects during memory leak investigation.
import gc
gc.set_debug(gc.DEBUG_STATS)
unreachable = gc.collect()
print(f'Collected {unreachable} unreachable objects')
print(gc.garbage) # objects that couldn't be collected
Real-world example
A long-running service investigates a slow memory leak by periodically calling gc.collect() and logging gc.garbage to identify which custom classes with __del__ methods are stuck in uncollectable cycles.
Debugging & Profiling;Multiple Inheritance & MRO
What does it mean that Python has 'automatic' memory management, and what's the trade-off compared to manual memory management in languages like C?
BeginnerPython programmers don't need to explicitly allocate or free memory -- the interpreter handles allocation on object creation and deallocation via reference counting and garbage collection. The trade-off is less direct control and some performance/predictability overhead compared to manual memory management, but it eliminates entire classes of bugs like use-after-free, double-free, and most memory leaks.
# No malloc/free needed
data = [i**2 for i in range(1000000)]
# automatically freed when 'data' goes out of scope or is reassigned
Real-world example
A team migrating a performance-critical component from Python to C++ has to now manually manage memory lifetimes that Python previously handled transparently, introducing new categories of bugs.
Concurrency (asyncio/threading/multiprocessing);Debugging & Profiling
Showing 1–10 of 15