Memory Optimization Strategies
CPython employs several internal strategies to minimize allocation overhead for common immutable values.
Small Integer Caching
The interpreter pre-allocates integers from -5 through 256. Any assignment within this band references a single shared object rather than requesting new memory.
m = 42
n = 42
assert m is n # True, identical object
Single-character strings receive similar treatment.
Large Integer Handling
Values outside the cached window always yield fresh objects.
m = 12345
n = 12345
assert m is not n # True, distinct allocations
String Interning
When multiple variables bind to the same string literal, CPython stores one copy and elevates its reference count. Strings containing whitespace or constructed at runtime are typically not interned automatically.
tag_a = "BenchmarkData"
tag_b = "BenchmarkData"
tag_c = "BenchmarkData"
Rather than allocating three independent buffers, the runtime interns the literal and tracks references to it.
Garbage Colection Architecture
CPython manages memory primarily through reference counting, supplemented by a generational cyclic garbage collector.
Reference Counting Mechanics
Every Python object carries an ob_refcnt field inside its PyObject header. Macros such as Py_INCREF and Py_DECREF adjust this counter. When ob_refcnt reaches zero, the destructor executes immediately.
typedef struct_object {
int ob_refcnt;
struct_typeobject *ob_type;
} PyObject;
Advantages:
- Deterministic disposal
- Memory is reclaimed continuously rather than in bursts
- Minimal pause times
Disadvantages:
- Counter updates incur small runtime costs
- Reference cycles prevent automatic reclamation
node_alpha = []
node_beta = []
node_alpha.append(node_beta)
node_beta.append(node_alpha)
After external references disappear, both lists still hold a count of one, creating an unreachable island that pure reference counting cannot free.
You can inspect an object's count with sys.getrefcount, though the result includes the temporary reference created by the function call itself:
import sys
obj = "demo"
print(sys.getrefcount(obj)) # actual references + 1
Generational Cyclic GC
To resolve cycles, CPython maintains three generations of object lists. New objects enter Generation 0. Survivors promote to Generation 1, and later to Generation 2.
import gc
print(gc.get_count()) # e.g., (590, 8, 0)
print(gc.get_threshold()) # e.g., (700, 10, 10)
The three-element counter tracks allocation deltas since the last collection. The default thresholds (700, 10, 10) work as follows:
- When the first counter hits 700, Generation 0 is swept, survivors move to Generation 1, and the counter resets.
- Each Generation 0 sweep increments the second counter. When it reaches 10, Generations 0 and 1 are collected together, survivors move to Generation 2, and the second counter resets.
- Each Generation 1 sweep increments the third counter. When it reaches 10, all three generations are scanned.
This staged approach concentrates effort on short-lived objects while scanning long-lived objects less frequently.
Manual Collection
Automatic collection can be disabled or invoked explicitly.
import gc
class LoopNode:
def __init__(self):
self.link = None
def cycle_demo():
gc.disable()
try:
while True:
left = LoopNode()
right = LoopNode()
left.link = right
right.link = left
del left
del right
gc.collect() # Force immediate cycle detection
finally:
gc.enable()
Built-in Attributes and Interception
Python classes expose numerous dunder attributes for introspection and behavior customization.
| Attribute | Purpose | Invocation |
|---|---|---|
__init__ |
Instance initializer | After __new__, during assignment |
__new__ |
Instance creation | When calling the class constructor |
__class__ |
Owner type | instance.__class__ |
__str__ |
Human-readable representation | print(instance) |
__repr__ |
Unambiguous representation | repr(instance) |
__del__ |
Finalizer | del instance |
__dict__ |
Per-instance namespace | vars(instance) |
__doc__ |
Class documentation | help(class) |
__getattribute__ |
Attribute access interceptor | Every attribute lookup |
__bases__ |
Direct superclasses | class.__bases__ |
Attribute Interception Example
class Curriculum:
def __init__(self, primary):
self.primary = primary
self.secondary = 'algorithms'
def __getattribute__(self, name):
if name == 'primary':
print('audit: primary accessed')
return 'redirected topic'
return super().__getattribute__(name)
c = Curriculum('networking')
print(c.primary) # audit: primary accessed -> redirected topic
print(c.secondary) # algorithms
Caution: Inside
__getattribute__, never useself.attrdirectly; doing so triggers infinite recursion. Always route throughsuper().__getattribute__(name).
Essential Built-in Functions
range
Generates arithmetic progressions. In Python 3 it returns a lazy sequence; wrap with list for eager evaluation.
seq = range(3, 15, 3) # 3, 6, 9, 12
vals = [v + 5 for v in range(4)] # [5, 6, 7, 8]
map
Applies a transformation to every element of one or more iterables.
squares = list(map(lambda n: n ** 2, [3, 5, 7]))
pairs = list(map(lambda idx, day: (idx, day),
range(7),
['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']))
filter
Selects items satisfying a predicate.
odds = list(filter(lambda n: n % 2 == 1, range(8)))
# [1, 3, 5, 7]
reduce
Accumulates a sequence into a single value. In Python 3 it resides in functools.
from functools import reduce
total = reduce(lambda acc, cur: acc + cur, [3, 6, 9, 12])
# 30
concat = reduce(lambda acc, cur: acc + cur, ['aa', 'bb', 'cc'], 'zz')
# 'zzaabbcc'
Set Operations
Sets store unique hashable elements and support mathematical operations.
group_x = set('pqrs')
group_y = set(['j', 'k', 'l', 'l', 'm'])
group_z = set('rnop')
intersect = group_x & group_z
union = group_x | group_y
diff = group_x - group_z
sym_diff = group_x ^ group_z
Standard and Extended Libraries
Frequently Used Standard Modules
| Module | Role |
|---|---|
os |
Operating system interface |
sys |
Interpreter runtime environment |
json |
JSON encoding and decoding |
logging |
Diagnostics and event tracking |
threading / multiprocessing |
Concurrency primitives |
copy |
Shallow and deep copies |
datetime / time / calendar |
Temporal utilities |
hashlib |
Cryptographic hashing |
re |
Regular expression engine |
socket |
Low-level network interface |
shutil |
High-level file operations |
Hashing Example
import hashlib
import datetime
secret = 'AppKey'
now = datetime.datetime.now()
payload = f'{secret}{now:%Y%m%d}'
digester = hashlib.sha256()
digester.update(payload.encode('utf-8'))
token = digester.hexdigest()
print(token)
Notable Third-Party Ecosystem
| Package | Domain |
|---|---|
requests / urllib3 |
HTTP client layer |
scrapy |
Web crawling framework |
beautifulsoup4 |
HTML/XML parsing |
redis |
Cache and queue backend |
Pillow |
Image manipulation |
pymysql / psycopg2 |
RDBMS connectors |
pymongo |
MongoDB driver |
numpy / pandas / scipy |
Scientific computing |
matplotlib |
Plotting and visualization |
scikit-learn |
Machine learning |
django / flask / fastapi |
Web application frameworks |
Serving static files locally without a framework:
# Python 2
python -m SimpleHTTPServer 8080
# Python 3
python -m http.server 8080
Debugging with pdb
The built-in pdb module provides interactive source-level debugging.
| Command | Alias | Effect |
|---|---|---|
break |
b |
Set a breakpoint |
continue |
c |
Resume execution |
list |
l |
Display surrounding source |
step |
s |
Step into a function call |
next |
n |
Step over a line |
return |
r |
Run until current function returns |
print |
p |
Evaluate and display an expression |
args |
a |
Show argument values |
quit |
q |
Terminate the debugger |
bt |
— | Print stack trace |
PEP 8 Coding Conventions
Consistency across a codebase improves readability and reduces maintenance burden.
Layout Rules
- Indent with four spaces; never mix tabs and spaces.
- Limit lines to 79 characters; docstrings and comments to 72.
- Separate top-level definitions with two blank lines; methods within a class with one.
- Place imports at the top, grouped into standard library, third-party, and local sections.
Whitespace Guidelines
Avoid extraneous spaces inside parentheses, brackets, or braces:
# Preferred
process(data[0], {flag: True})
coords[index:offset + 1]
result = base * multiplier - offset
# Discouraged
process( data[ 0 ], { flag: True } )
coords[index : offset + 1]
Binary operators should have one space on each side, though priority grouping may omit internal spaces:
# Preferred
i = i + 1
submitted += 1
hypot = x*x + y*y
c = (a+b) * (a-b)
Naming Avoidances
Do not use l (lowercase letter el), O (uppercase oh), or I (uppercase eye) as single-character identifiers; in many fonts they are indistinguishable from numerals.