A Comprehension is a concise, highly optimized syntactic construct in Python used to generate a new collection (list, set, or dictionary) from an existing iterable in a single line of readable code. Under the hood, Python executes comprehensions at C-level speed, outperforming traditional multi-line for loops that repeatedly call .append().
for x in range(1, 11)if x % 2 == 0x ** 2| Type | Syntax Blueprint | Output Structure | Primary Characteristic |
|---|---|---|---|
| List Comprehension | [expr for item in iter if cond] |
Ordered Python list |
Allows duplicates; preserves original order. |
| Set Comprehension | {expr for item in iter if cond} |
Unordered Python set |
Automatically eliminates duplicates! |
| Dict Comprehension | {k_expr: v_expr for item in iter} |
Key-Value Python dict |
Constructs lookup mappings directly. |
# 1. List Comprehension: Filter even numbers and square them
numbers = [1, 2, 3, 4, 5, 6, 7, 8]
even_squares = [x**2 for x in numbers if x % 2 == 0]
print("List Comprehension (Even Squares):", even_squares)
# 2. Set Comprehension: Extract unique word lengths (eliminates duplicate lengths)
words = ["apple", "cat", "banana", "dog", "fig", "apple"]
unique_lengths = {len(w) for w in words}
print("Set Comprehension (Unique Word Lengths):", unique_lengths)
# 3. Dictionary Comprehension: Map student names to letter grades
raw_scores = {"Aman": 88, "Pooja": 94, "Rahul": 62, "Sneha": 78}
grades = {name: ("A+" if score >= 90 else "A" if score >= 75 else "B")
for name, score in raw_scores.items()}
print("Dict Comprehension (Student Grades):", grades)
• An Iterable is any Python object capable of returning its members one at a time (e.g. lists, tuples, strings, dictionaries). It implements the __iter__() method.
• An Iterator is an object representing a stream of data that remembers its internal state during traversal. It implements The Iterator Protocol: both __iter__() and __next__().
Calling next(it) returns the next item in sequence. When the stream is exhausted, it raises a StopIteration exception to signal completion.
__iter__().__next__().StopIteration and cleanly terminates loop!# Building a Custom Countdown Iterator implementing the protocol
class Countdown:
def __init__(self, start):
self.current = start
def __iter__(self):
return self # An iterator must return itself
def __next__(self):
if self.current <= 0:
raise StopIteration # Mandatory protocol signal when finished
val = self.current
self.current -= 1
return val
print("Running custom Countdown iterator:")
counter = Countdown(3)
for count in counter:
print(f" T-minus {count}...")
print(" 🚀 Liftoff!")
A Generator is a special function that produces an iterator stream using the yield keyword instead of return.
• When a standard function hits return, it terminates completely, and all its local variables in RAM are destroyed.
• When a generator function hits yield, it produces a value and pauses execution, preserving its entire local state and execution pointer in memory. When next() is called again, it resumes directly on the very next line!
Memory Advantage (Lazy Evaluation): If you generate 10,000,000 numbers in a list, it consumes ~800 Megabytes of RAM. A generator generates values one at a time on demand, consuming only ~120 Bytes of RAM!
[0, 1, 2, ... 10,000,000]import sys
# Generator function using yield
def fibonacci_stream(limit):
a, b = 0, 1
count = 0
while count < limit:
yield a # Pauses and returns current number!
a, b = b, a + b # Resumes here on next call
count += 1
fib_gen = fibonacci_stream(6)
print("Type of fib_gen:", type(fib_gen).__name__)
print("Values produced one by one:")
for val in fib_gen:
print(f" Generated -> {val}")
# RAM Comparison Proof: List vs Generator Expression
huge_list = [x for x in range(100000)]
huge_gen = (x for x in range(100000))
print("\nRAM Footprint for 100,000 items:")
print(f" List Memory Size : {sys.getsizeof(huge_list):,} bytes")
print(f" Generator Memory Size : {sys.getsizeof(huge_gen)} bytes (Microscopic!)")
A Decorator is a design pattern in Python that allows you to dynamically inject new functionality into an existing function or method without modifying its original source code.
In Python, functions are first-class citizens (they can be assigned to variables, passed as arguments into other functions, and returned from functions). A decorator takes a target function as input, wraps it inside an inner function, and returns the modified wrapper function.
import time
import functools
# Building an execution benchmark decorator
def execution_timer(func):
@functools.wraps(func) # Preserves original function name and docstrings
def wrapper(*args, **kwargs):
start_time = time.perf_counter()
result = func(*args, **kwargs) # Call the original function
duration = time.perf_counter() - start_time
print(f"⏱️ [BENCHMARK] Function '{func.__name__}' took {duration:.6f} seconds.")
return result
return wrapper
# Applying the decorator using @ syntax
@execution_timer
def calculate_sum_of_squares(n):
return sum(i**2 for i in range(n))
ans = calculate_sum_of_squares(500000)
print(f"Calculation Result: {ans}")
A Regular Expression is a sequence of characters that forms a search pattern. It is used for validating user input (e.g. verifying email formats, phone numbers, or passwords), extracting substrings from text corpora, and performing complex text replacements.
| Symbol | Meaning | Example Match |
|---|---|---|
\d |
Any single numeric digit [0-9] |
\d{3} matches "123" |
\w |
Any word character (alphanumeric + underscore) | \w+ matches "user_101" |
^ and $ |
Anchors: ^ starts string, $ ends string |
^\d{10}$ matches exact 10-digit phone |
+ / * |
Quantifiers: + is 1 or more, * is 0 or more |
[a-z]+ matches one or more lowercase letters |
import re
sample_text = "Contact support at dev@devascent.me or admissions@academy.edu by 2026-10-15."
# 1. re.findall(): Extract all email addresses matching regex
email_pattern = r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}'
found_emails = re.findall(email_pattern, sample_text)
print("Extracted Emails:", found_emails)
# 2. re.search(): Find and parse date pattern (YYYY-MM-DD)
date_pattern = r'(\d{4})-(\d{2})-(\d{2})'
match = re.search(date_pattern, sample_text)
if match:
year, month, day = match.groups()
print(f"Found Date -> Year: {year}, Month: {month}, Day: {day}")
In Python, writing list_b = list_a does NOT copy the list! It merely creates a second reference variable pointing to the exact same memory address in RAM. Modifying list_b will inadvertently corrupt list_a!
To actually copy data, Python provides the copy module with two distinct algorithms:
copy.copy()): Constructs a new collection object, but inserts references to the child objects found in the original. If a nested list is modified, both copies see the modification!copy.deepcopy()): Recursively clones the entire object graph, creating completely independent copies of all nested lists, dictionaries, and custom objects in RAM.import copy
original = [["A", "B"], [1, 2]]
shallow = copy.copy(original)
deep = copy.deepcopy(original)
# Modify a nested item inside the original list
original[0][0] = "CORRUPTED"
print("Original List :", original)
print("Shallow Copy :", shallow, "<- Mutated! (Shares inner reference)")
print("Deep Copy :", deep, "<- Protected! (Completely independent clone)")
Python manages memory automatically without requiring manual free() or malloc() through two cooperative systems:
0, CPython instantly frees its memory back to the system!object_A.child = object_B and object_B.parent = object_A; their reference counts will never drop to 0 even if deleted). Python's cyclic garbage collector groups objects into 3 generations (Gen 0, 1, 2) and periodically detects and frees unreferenced cycles.import sys
import gc
# 1. Reference Counting Inspection
sample_obj = [10, 20, 30]
print("Initial Reference Count:", sys.getrefcount(sample_obj) - 1)
alias_ref = sample_obj # Create another reference
print("Ref Count after alias :", sys.getrefcount(sample_obj) - 1)
del alias_ref # Delete reference
print("Ref Count after del :", sys.getrefcount(sample_obj) - 1)
# 2. Inspecting Generational Garbage Collection counts
print("\nGarbage Collector Generation Thresholds (Gen0, Gen1, Gen2):", gc.get_threshold())
print("Current Uncollected Object Counts in Generations :", gc.get_count())
This capstone integrates Unit 13 concepts: a generator for lazy log streaming, a decorator for benchmark timing, regex for parsing IP addresses and HTTP status codes, datetime for audit timestamps, and deepcopy for protecting metric state.
import re
import time
import copy
import datetime
# 1. Benchmark Decorator
def timed_pipeline(func):
def wrapper(*args, **kwargs):
t0 = time.perf_counter()
res = func(*args, **kwargs)
duration = time.perf_counter() - t0
print(f"\n[AUDIT] Engine '{func.__name__}' completed in {duration:.6f}s")
return res
return wrapper
# 2. Generator Function for Lazy Log Streaming
def raw_log_stream():
logs = [
'192.168.1.10 - [GET /api/v1/students] 200 OK',
'10.0.0.52 - [POST /api/v1/auth] 401 UNAUTHORIZED',
'192.168.1.10 - [GET /api/v1/reports] 200 OK',
'172.16.0.4 - [GET /favicon.ico] 404 NOT_FOUND'
]
for entry in logs:
yield entry
# 3. Processing Pipeline integrating Regex & Dict Comprehension
@timed_pipeline
def analyze_server_logs():
ip_pattern = r'^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})'
status_pattern = r'\] (\d{3})'
records = []
for line in raw_log_stream():
ip_match = re.search(ip_pattern, line)
status_match = re.search(status_pattern, line)
if ip_match and status_match:
records.append({"ip": ip_match.group(1), "status": int(status_match.group(1))})
# Dict Comprehension: Count occurrences of each HTTP status
status_counts = {code: sum(1 for r in records if r["status"] == code)
for code in {r["status"] for r in records}}
# Deep copy to preserve snapshot
snapshot = copy.deepcopy(status_counts)
return snapshot
summary = analyze_server_logs()
print("Parsed HTTP Status Summary:", summary)
return terminates the function, returns a single value, and clears all local variables from RAM.yield produces a value, pauses function execution, and preserves the execution state and local variables in memory so execution can resume seamlessly on the next call.
__iter__() and can produce an iterator (e.g., list, tuple).__iter__() and __next__(), producing values sequentially until raising StopIteration.
@decorator_name syntax.
copy.copy()): Copies the outer container, but inserts references to the original nested objects. Modifying nested data affects both copies.copy.deepcopy()): Recursively duplicates all nested objects, creating an independent clone in memory.
[x for x in data] evaluates eagerly and creates the entire list in RAM at once.(x for x in data) evaluates lazily, producing values on demand with constant, minimal memory usage.